<!doctype html>



  


<html class="theme-next pisces use-motion" lang="zh-Hans">
<head>
  <meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"/>



<meta http-equiv="Cache-Control" content="no-transform" />
<meta http-equiv="Cache-Control" content="no-siteapp" />















  
  
  <link href="/lib/fancybox/source/jquery.fancybox.css?v=2.1.5" rel="stylesheet" type="text/css" />




  
  
  
  

  
    
    
  

  

  

  

  

  
    
    
    <link href="//fonts.googleapis.com/css?family=Lato:300,300italic,400,400italic,700,700italic&subset=latin,latin-ext" rel="stylesheet" type="text/css">
  






<link href="/lib/font-awesome/css/font-awesome.min.css?v=4.6.2" rel="stylesheet" type="text/css" />

<link href="/css/main.css?v=5.1.0" rel="stylesheet" type="text/css" />


  <meta name="keywords" content="kniost, NexT" />





  <link rel="alternate" href="/atom.xml" title="结网之路" type="application/atom+xml" />




  <link rel="shortcut icon" type="image/x-icon" href="/favicon.ico?v=5.1.0" />






<meta name="description" content="kniost 的成长与疑惑">
<meta property="og:type" content="website">
<meta property="og:title" content="结网之路">
<meta property="og:url" content="https://kniost.github.io/page/2/index.html">
<meta property="og:site_name" content="结网之路">
<meta property="og:description" content="kniost 的成长与疑惑">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="结网之路">
<meta name="twitter:description" content="kniost 的成长与疑惑">



<script type="text/javascript" id="hexo.configurations">
  var NexT = window.NexT || {};
  var CONFIG = {
    root: '/',
    scheme: 'Pisces',
    sidebar: {"position":"right","display":"post","offset":12,"offset_float":0,"b2t":false,"scrollpercent":false},
    fancybox: true,
    motion: true,
    duoshuo: {
      userId: '0',
      author: '博主'
    },
    algolia: {
      applicationID: '',
      apiKey: '',
      indexName: '',
      hits: {"per_page":10},
      labels: {"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}
    }
  };
</script>



  <link rel="canonical" href="https://kniost.github.io/page/2/"/>





  <title> 结网之路 </title>
</head>

<body itemscope itemtype="http://schema.org/WebPage" lang="zh-Hans">

  














  
  
    
  

  <div class="container one-collumn sidebar-position-right 
   page-home 
 ">
    <div class="headband"></div>

    <header id="header" class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-wrapper">
  <div class="site-meta ">
    

    <div class="custom-logo-site-title">
      <a href="/"  class="brand" rel="start">
        <span class="logo-line-before"><i></i></span>
        <span class="site-title">结网之路</span>
        <span class="logo-line-after"><i></i></span>
      </a>
    </div>
      
        <p class="site-subtitle">临渊羡鱼，不如退而结网</p>
      
  </div>

  <div class="site-nav-toggle">
    <button>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
    </button>
  </div>
</div>

<nav class="site-nav">
  

  
    <ul id="menu" class="menu">
      
        
        <li class="menu-item menu-item-home">
          <a href="/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-home"></i> <br />
            
            首页
          </a>
        </li>
      
        
        <li class="menu-item menu-item-categories">
          <a href="/categories" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-th"></i> <br />
            
            分类
          </a>
        </li>
      
        
        <li class="menu-item menu-item-archives">
          <a href="/archives" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-archive"></i> <br />
            
            归档
          </a>
        </li>
      
        
        <li class="menu-item menu-item-tags">
          <a href="/tags" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-tags"></i> <br />
            
            标签
          </a>
        </li>
      
        
        <li class="menu-item menu-item-about">
          <a href="/about" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-user"></i> <br />
            
            关于
          </a>
        </li>
      

      
    </ul>
  

  
</nav>



 </div>
    </header>

    <main id="main" class="main">
      <div class="main-inner">
        <div class="content-wrap">
          <div id="content" class="content">
            
  <section id="posts" class="posts-expand">
    
      

  

  
  
  

  <article class="post post-type-normal " itemscope itemtype="http://schema.org/Article">
    <link itemprop="mainEntityOfPage" href="https://kniost.github.io/2017-01-22-cj0ajvov300181js6n5cd88kw/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="kniost">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/images/avatar.jpg">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="结网之路">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
            
            
              
                
                <a class="post-title-link" href="/2017-01-22-cj0ajvov300181js6n5cd88kw/" itemprop="url">
                  Android编程权威指南（第二版）学习笔记（十九）—— 第19章 使用 SoundPool 播放音频
                </a>
              
            
          </h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2017-01-22T21:11:10+08:00">
                2017-01-22
              </time>
            

            

            
          </span>

          
            <span class="post-category" >
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/Android编程权威指南学习笔记/" itemprop="url" rel="index">
                    <span itemprop="name">Android编程权威指南学习笔记</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
              <span class="post-comments-count">
                <span class="post-meta-divider">|</span>
                <span class="post-meta-item-icon">
                  <i class="fa fa-comment-o"></i>
                </span>
                <a href="/2017-01-22-cj0ajvov300181js6n5cd88kw/#comments" itemprop="discussionUrl">
                  <span class="post-comments-count disqus-comment-count" data-disqus-identifier="2017-01-22-cj0ajvov300181js6n5cd88kw/" itemprop="commentCount"></span>
                </a>
              </span>
            
          

          
          

          

          

          

        </div>
      </header>
    


    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <blockquote>
<p>既然音频资源文件已准备就绪,现在就来学习如何播放这些.wav音频文件。Android的大部分音频API都比较低级,掌握它们不是那么容易。不过没关系,针对BeatBox应用,可以使用SoundPool这个特别定制的实用工具。 SoundPool能加载一批声音资源到内存中,并支持同时播放多个音频文件。</p>
<p>GitHub 地址：<br><a href="https://github.com/kniost/Android-Programming-Big-Nerd-Ranch-Guide-2nd/commit/4d670bf5680b3e2fbdc0a2ce46c5051cfad05fdc" target="_blank" rel="external">完成第19章</a></p>
</blockquote>
<h1 id="1-创建-SoundPool"><a href="#1-创建-SoundPool" class="headerlink" title="1. 创建 SoundPool"></a>1. 创建 SoundPool</h1><p>在 Lollipop 中引入了新的方式创建SoundPool:使用SoundPool.Builder。不过,为了兼容API 16 最低级别,只能选择使用SoundPool(int, int, int)这个老构造方法了。一个代码示例如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">if</span> (Build.VERSION.SDK_INT &gt;= <span class="number">21</span>) &#123;</div><div class="line">    mSoundPool = <span class="keyword">new</span> SoundPool.Builder()</div><div class="line">            .setMaxStreams(MAX_SOUNDS)</div><div class="line">            .setAudioAttributes(<span class="keyword">new</span> AudioAttributes.Builder()</div><div class="line">                    .setLegacyStreamType(AudioManager.STREAM_MUSIC)</div><div class="line">                    .build())</div><div class="line">            .build();</div><div class="line">&#125; <span class="keyword">else</span> &#123;</div><div class="line">    <span class="comment">// This old constructor is deprecated, but we need it for</span></div><div class="line">    <span class="comment">// compatibility.</span></div><div class="line">    mSoundPool = <span class="keyword">new</span> SoundPool(MAX_SOUNDS, AudioManager.STREAM_MUSIC, <span class="number">0</span>);</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<blockquote>
<p>对于老的 API 来说，</p>
<ul>
<li>第一个参数指定同时播放多少个音频。这里指定了5个。在播放5个音频时,如果尝试再播放 第6个,SoundPool会停止播放原来的音频。</li>
<li>第二个参数确定音频流类型。Android有很多不同的音频流,它们都有各自独立的音量控制 选项。这就是调低音乐音量,闹钟音量却不受影响的原因。打开文档,查看AudioManager类的 <code>AUDIO_*</code>常量,还可以看到其他控制选项。<code>STREAM_MUSIC</code>使用的是同音乐和游戏一样的音量控制。</li>
<li>最后一个参数指定采样率转换品质。参考文档说这个参数不起作用,所以这里传入0值。</li>
</ul>
</blockquote>
<h1 id="2-加载音频文件"><a href="#2-加载音频文件" class="headerlink" title="2. 加载音频文件"></a>2. 加载音频文件</h1><p>对于 SoundPool，加载音频文件时在播放前必须预先加载音频。SoundPool加载的音频文件都有自己的Integer类型ID。所以要在Sound类中添加mSoundId实例变量,并添加相应的Getter 和 Setter 方法管理这些ID。</p>
<p>加载一个声音，我们用的是 SoundPool 的 <code>public int load(AssetFileDescriptor afd, int priority)</code> 方法，这个方法需要一个 AssetFileDescriptor 对象和一个整型的 priority（暂时没用，建议设为1）。</p>
<p>AssetFileDescriptor 对象使用 AssetManager 的 openFd() 方法获取，传入的参数是音频文件的 Asset 路径。</p>
<p>所以整个加载的方法就是这么写的：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// BeatBox.java</span></div><div class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">load</span><span class="params">(Sound sound)</span> <span class="keyword">throws</span> IOException </span>&#123;</div><div class="line">    AssetFileDescriptor afd = mAssets.openFd(sound.getAssetPath());</div><div class="line">    <span class="keyword">int</span> soundId = mSoundPool.load(afd, <span class="number">1</span>);</div><div class="line">    sound.setSoundId(soundId);</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<p>然后在添加 Sound 列表的时候，一一加载每个声音。</p>
<h1 id="3-播放音频"><a href="#3-播放音频" class="headerlink" title="3. 播放音频"></a>3. 播放音频</h1><p>添加这样的一个播放方法和释放方法</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">play</span><span class="params">(Sound sound)</span> </span>&#123;</div><div class="line">    Integer soundId = sound.getSoundId();</div><div class="line">    <span class="comment">// Id 设置成 Integer 类就是为了判断 null 较为方便</span></div><div class="line">    <span class="keyword">if</span> (soundId == <span class="keyword">null</span>) &#123;</div><div class="line">        <span class="keyword">return</span>; </div><div class="line">    &#125;</div><div class="line">    <span class="comment">// play 函数的第一个参数是 sound 的 soundId</span></div><div class="line">    <span class="comment">// 应该是由 load() 方法返回的 id</span></div><div class="line">    <span class="comment">// 第二个是左声道的音量</span></div><div class="line">    <span class="comment">// 第三个是右声道的音量</span></div><div class="line">    <span class="comment">// 第四个是priority（无效）</span></div><div class="line">    <span class="comment">// 第五个是是否循环</span></div><div class="line">    <span class="comment">// 第六个是播放速度</span></div><div class="line">    mSoundPool.play(soundId, <span class="number">1.0f</span>, <span class="number">1.0f</span>, <span class="number">1</span>, <span class="number">0</span>, <span class="number">1.0f</span>);</div><div class="line">&#125;</div><div class="line"></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">release</span><span class="params">()</span> </span>&#123;</div><div class="line">    mSoundPool.release();</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<p>在点击 ViewHolder 的时候播放，在 onDestroy 的时候释放即可</p>
<h1 id="4-设备旋转和对象保存"><a href="#4-设备旋转和对象保存" class="headerlink" title="4.设备旋转和对象保存"></a>4.设备旋转和对象保存</h1><p>使用 retainInstance() 即可</p>

          
        
      
    </div>

    <div>
      
    </div>

    <div>
      
    </div>

    <div>
      
    </div>

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal " itemscope itemtype="http://schema.org/Article">
    <link itemprop="mainEntityOfPage" href="https://kniost.github.io/2017-01-21-cj0ajvov000151js6cxlggt6s/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="kniost">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/images/avatar.jpg">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="结网之路">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
            
            
              
                
                <a class="post-title-link" href="/2017-01-21-cj0ajvov000151js6cxlggt6s/" itemprop="url">
                  Android编程权威指南（第二版）学习笔记（十八）—— 第18章 Assets
                </a>
              
            
          </h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2017-01-21T20:46:10+08:00">
                2017-01-21
              </time>
            

            

            
          </span>

          
            <span class="post-category" >
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/Android编程权威指南学习笔记/" itemprop="url" rel="index">
                    <span itemprop="name">Android编程权威指南学习笔记</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
              <span class="post-comments-count">
                <span class="post-meta-divider">|</span>
                <span class="post-meta-item-icon">
                  <i class="fa fa-comment-o"></i>
                </span>
                <a href="/2017-01-21-cj0ajvov000151js6cxlggt6s/#comments" itemprop="discussionUrl">
                  <span class="post-comments-count disqus-comment-count" data-disqus-identifier="2017-01-21-cj0ajvov000151js6cxlggt6s/" itemprop="commentCount"></span>
                </a>
              </span>
            
          

          
          

          

          

          

        </div>
      </header>
    


    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <blockquote>
<p>本章介绍了 assets。assets 是有别于 resources 的另一种资源打包方式，可以被看作随应用打包的微型文件系统，支持任意层次的文件目录结构。因为这个优点，类似游戏这样需要加载大量图片和声音资源的应用通常都会使用它。</p>
<p>GitHub 地址：<br><a href="https://github.com/kniost/Android-Programming-Big-Nerd-Ranch-Guide-2nd/commit/5d4a0a8544ae083af20fa265ee7a79a201865d8d" target="_blank" rel="external">完成18章</a></p>
</blockquote>
<p>这章需要用到一个 RecyclerView，其中每个 ViewHolder 对应一个音频文件。如果使用 resources 来管理声音文件，那么 Android 一个一个处理效率将极其低下（毕竟声音是时间敏感的），所以我们使用 Assets 来处理。</p>
<h1 id="1-导入-Assets"><a href="#1-导入-Assets" class="headerlink" title="1 导入 Assets"></a>1 导入 Assets</h1><p>在模块的根目录下新建 assets 文件（或者右键点击模块的根目录新建），然后建立需要的资源文件夹并放入资源。</p>
<h1 id="2-处理-Assets"><a href="#2-处理-Assets" class="headerlink" title="2 处理 Assets"></a>2 处理 Assets</h1><p>assets 导入后，我们还要能在应用中进行<strong>定位、管理记录以及播放</strong>。这需要新建一个名为 BeatBox 的资源管理类，代码和相应注释如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">BeatBox</span> </span>&#123;</div><div class="line">	<span class="comment">// TAG 变量用于记录日志</span></div><div class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String TAG = <span class="string">"BeatBox"</span>;</div><div class="line"></div><div class="line">	<span class="comment">// 用于存储声音资源文件目录名</span></div><div class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String SOUNDS_FOLDER = <span class="string">"sample_sounds"</span>;</div><div class="line"></div><div class="line">	<span class="comment">// 访问 assets 需要用到 AssetManager 对象</span></div><div class="line">    <span class="keyword">private</span> AssetManager mAssets;</div><div class="line"></div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="title">BeatBox</span><span class="params">(Context context)</span> </span>&#123;</div><div class="line">    <span class="comment">// 在初始化的时候获取 assets 的管理对象</span></div><div class="line">    <span class="comment">// 不用在意 Context 是哪个，因为</span></div><div class="line">    <span class="comment">// 所有的 AssetManager 管理的都是一套 assets 资源</span></div><div class="line">        mAssets = context.getAssets();</div><div class="line">        loadSounds();</div><div class="line">    &#125;</div><div class="line"></div><div class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">loadSounds</span><span class="params">()</span> </span>&#123;</div><div class="line">        String[] soundNames = <span class="keyword">new</span> String[]&#123;&#125;;</div><div class="line">        <span class="keyword">try</span> &#123;</div><div class="line">        	<span class="comment">// 获取所有文件的名字</span></div><div class="line">            soundNames = mAssets.list(SOUNDS_FOLDER);</div><div class="line">            Log.i(TAG, <span class="string">"Found "</span> + soundNames.length + <span class="string">" sounds"</span>);</div><div class="line">        &#125; <span class="keyword">catch</span> (IOException ioe) &#123;</div><div class="line">            Log.e(TAG, <span class="string">"Could not list assets"</span>, ioe);</div><div class="line">        &#125;</div><div class="line">    &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<h1 id="3-使用-Assets"><a href="#3-使用-Assets" class="headerlink" title="3 使用 Assets"></a>3 使用 Assets</h1><p>获取到资源文件名之后，要将其展示给用户，最终还需要播放这些声音文件。所以，我们得创建一个对象，让它管理<strong>资源文件名、用户应该看到的文件名以及其他一些相关信息</strong>。在这里，我们创建了一个叫 Sound 的类用来管理一个声音：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Sound</span> </span>&#123;</div><div class="line">    <span class="keyword">private</span> String mAssetPath;</div><div class="line">    <span class="keyword">private</span> String mName;</div><div class="line"></div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="title">Sound</span><span class="params">(String assetPath)</span> </span>&#123;</div><div class="line">        mAssetPath = assetPath;</div><div class="line">        <span class="comment">// 将文件路径用"/"分割</span></div><div class="line">        String[] components = assetPath.split(<span class="string">"/"</span>);</div><div class="line">        <span class="comment">// 取最后一个，即文件名</span></div><div class="line">        String filename = components[components.length - <span class="number">1</span>];</div><div class="line">        <span class="comment">// 在知道文件名结尾是 .wav 的情况下，直接将 .wav 替换为空</span></div><div class="line">        mName = filename.replace(<span class="string">".wav"</span>, <span class="string">""</span>);</div><div class="line">    &#125;</div><div class="line"></div><div class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">getAssetPath</span><span class="params">()</span> </span>&#123;</div><div class="line">        <span class="keyword">return</span> mAssetPath;</div><div class="line">    &#125;</div><div class="line"></div><div class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">getName</span><span class="params">()</span> </span>&#123;</div><div class="line">        <span class="keyword">return</span> mName;</div><div class="line">    &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<p>然后在 BeatBox 类中获取所有的 Sound 对象，这样我们就有了 RecyclerView 的数据集，然后就能够绑定声音和 ViewHolder 了。</p>
<h1 id="4-访问-Assets"><a href="#4-访问-Assets" class="headerlink" title="4 访问 Assets"></a>4 访问 Assets</h1><p>Sound 对象定义了 assets 文件路径。但是尝试使用 File 对象打开资源文件是行不通的;<br>正确的方式是使用 AssetManager:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div></pre></td><td class="code"><pre><div class="line">String assetPath = sound.getAssetPath();</div><div class="line">InputStream soundData = mAssets.open(assetPath);</div></pre></td></tr></table></figure>
<p>这样才能得到标准的 InputStream 数据流。随后，和 Java 中的其他 InputStream 一样，该怎么用就怎么用。<br>不过，有些 API 可能还会需要 FileDescriptor。(下一章的 SoundPool 类会用到。)这也好办， 如下列代码所示，改调用 AssetManager.openFd(String)方法就行了。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line">String assetPath = sound.getAssetPath();</div><div class="line"><span class="comment">// AssetFileDescriptors are different from FileDescriptors,</span></div><div class="line">AssetFileDescriptor assetFd = mAssets.openFd(assetPath);</div><div class="line"><span class="comment">// but you get can a regular FileDescriptor easily if you need to.</span></div><div class="line">FileDescriptor fd = assetFd.getFileDescriptor();</div></pre></td></tr></table></figure>

          
        
      
    </div>

    <div>
      
    </div>

    <div>
      
    </div>

    <div>
      
    </div>

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal " itemscope itemtype="http://schema.org/Article">
    <link itemprop="mainEntityOfPage" href="https://kniost.github.io/2017-01-15-cj0ajvouv000r1js64b00vv1f/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="kniost">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/images/avatar.jpg">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="结网之路">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
            
            
              
                
                <a class="post-title-link" href="/2017-01-15-cj0ajvouv000r1js64b00vv1f/" itemprop="url">
                  Android编程权威指南（第二版）学习笔记（十七）—— 第17章 Master-Detail 用户界面
                </a>
              
            
          </h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2017-01-15T18:02:10+08:00">
                2017-01-15
              </time>
            

            

            
          </span>

          
            <span class="post-category" >
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/Android编程权威指南学习笔记/" itemprop="url" rel="index">
                    <span itemprop="name">Android编程权威指南学习笔记</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
              <span class="post-comments-count">
                <span class="post-meta-divider">|</span>
                <span class="post-meta-item-icon">
                  <i class="fa fa-comment-o"></i>
                </span>
                <a href="/2017-01-15-cj0ajvouv000r1js64b00vv1f/#comments" itemprop="discussionUrl">
                  <span class="post-comments-count disqus-comment-count" data-disqus-identifier="2017-01-15-cj0ajvouv000r1js64b00vv1f/" itemprop="commentCount"></span>
                </a>
              </span>
            
          

          
          

          

          

          

        </div>
      </header>
    


    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <blockquote>
<p>本章介绍了如何写一个双版面 fragment 的布局，并对符合要求的设备进行适配，还介绍了回调接口的使用。<br>GitHub 地址：<br><a href="https://github.com/kniost/Android-Programming-Big-Nerd-Ranch-Guide-2nd/commit/6b11d46262c10d7a6a143e2e2be225bc99851852" target="_blank" rel="external">完成17章</a></p>
</blockquote>
<p>对平板设备来说，使用主从用户界面将会得到更好的体验，在这章我们将对其使用，传递数据的方式进行探究。</p>
<h1 id="1-增加布局灵活性"><a href="#1-增加布局灵活性" class="headerlink" title="1. 增加布局灵活性"></a>1. 增加布局灵活性</h1><p>要实现双版面的布局，需要完成如下任务：</p>
<ol>
<li>修改 SingleFragmentActivity，使其不再硬编码实例化布局</li>
<li>创建包含两个 fragment 容器的布局</li>
<li>修改 CrimeListActivity，实现在手机设备上实例化单版面布局，在平板设备上实例化双版面布局</li>
</ol>
<h2 id="1-1-修改抽象类-SingleFragmentActivity"><a href="#1-1-修改抽象类-SingleFragmentActivity" class="headerlink" title="1.1 修改抽象类 SingleFragmentActivity"></a>1.1 修改抽象类 SingleFragmentActivity</h2><p>在其中加入一个 protected 方法，返回 activity 需要的 ResId，这样对于继承 SingleFragmentActivity 的 activity 可以重写该函数以返回自己需要的 ResId。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line"><span class="meta">@LayoutRes</span></div><div class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">int</span> <span class="title">getLayoutResId</span><span class="params">()</span> </span>&#123;</div><div class="line">    <span class="keyword">return</span> R.layout.activity_fragment;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<h2 id="1-2-使用别名资源"><a href="#1-2-使用别名资源" class="headerlink" title="1.2 使用别名资源"></a>1.2 使用别名资源</h2><p>我们想让最小屏幕宽度 600dp 的设备使用双版面界面，其他的使用单版面界面，那么对于不同的设备，使用的布局就不同。要让不同的设备使用不同的布局资源，有两种方法：</p>
<ol>
<li>让 res/layout/目录中的文件使用资源修饰符。如果想使用<code>activity_masterdetail.xml</code>布局文件， 就需要将<code>activity_fragment.xml</code>的内容复制到<code>res/layout/activity_masterdetail.xml</code>中，将<code>activity_twopane.xml</code>的内容复制到<code>res/layout-sw600dp/activity_masterdetail.xml</code>中。这样做最明显的缺点就是数据冗余，因为每个布局文件都要复制一份。</li>
<li><p>使用别名资源。别名资源是一种指向其他资源的特殊资源。它存放在 res/values/目录下，并按照约定定义在 refs.xml 文件中。比如在默认的 values 文件夹下面新建一个 refs.xml，然后写入代码：</p>
 <figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line">&lt;?xml version="1.0" encoding="utf-8"?&gt;</div><div class="line"><span class="tag">&lt;<span class="name">resources</span>&gt;</span></div><div class="line">	<span class="tag">&lt;<span class="name">item</span> <span class="attr">name</span>=<span class="string">"activity_masterdetail"</span> <span class="attr">type</span>=<span class="string">"layout"</span>&gt;</span>@layout/activity_fragment<span class="tag">&lt;/<span class="name">item</span>&gt;</span></div><div class="line"><span class="tag">&lt;/<span class="name">resources</span>&gt;</span></div></pre></td></tr></table></figure>
<p> 再新建一个最小宽度600dp 的 refs.xml(即在 values-sw600dp 目录下)，写入双版面的 layout 资源：</p>
 <figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line">&lt;?xml version="1.0" encoding="utf-8"?&gt;</div><div class="line"><span class="tag">&lt;<span class="name">resources</span>&gt;</span></div><div class="line">	<span class="tag">&lt;<span class="name">item</span> <span class="attr">name</span>=<span class="string">"activity_masterdetail"</span> <span class="attr">type</span>=<span class="string">"layout"</span>&gt;</span>@layout/activity_twopane<span class="tag">&lt;/<span class="name">item</span>&gt;</span></div><div class="line"><span class="tag">&lt;/<span class="name">resources</span>&gt;</span></div></pre></td></tr></table></figure>
<p> 这样，在 CrimeListActivity 中只要引用 <code>R.layout.activity_masterdetail</code> 即可</p>
</li>
</ol>
<h1 id="2-Activity：Fragment-的托管者"><a href="#2-Activity：Fragment-的托管者" class="headerlink" title="2. Activity：Fragment 的托管者"></a>2. Activity：Fragment 的托管者</h1><p>为了保证 fragment 的独立性，即不需要了解其托管者的工作，但要想在 fragment 生命周期没有结束的时候传递数据出去，就要使用回调接口。</p>
<blockquote>
<p>回调就相当于一个委托，首先 fragment 自己定义回调的接口，托管的 acitivity 来实现这个接口，接着 fragment 需要持有实现了自己定义接口的对象，以便自己可以实时调用。</p>
<p>对于一个回调接口而言，fragment 只要求实现这个接口的类在函数里要做的是什么，却不知道实现类到底会做什么，每个实现类有自己的方法来实现。</p>
<h1 id="2-1-CrimeListFragment-的回调接口"><a href="#2-1-CrimeListFragment-的回调接口" class="headerlink" title="2.1 CrimeListFragment 的回调接口"></a>2.1 CrimeListFragment 的回调接口</h1><p>对于 CrimeListFragment，其所能响应的就是点击列表中的某一项，那么它的回调接口定义如下：</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">Callbacks</span> </span>&#123;</div><div class="line">	<span class="function"><span class="keyword">void</span> <span class="title">onCrimeSelected</span><span class="params">(Crime crime)</span></span>;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<p>然后应该在需要托管的 Activity 中实现该接口，在这里是 CrimeListActivity：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// 省略 implement 以节约版面</span></div><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onCrimeSelected</span><span class="params">(Crime crime)</span> </span>&#123;</div><div class="line">	<span class="comment">// 如果发现布局里没有包含详情 fragment 容器的 id，</span></div><div class="line">	<span class="comment">// 就启动单独的 activity 用于显示详情</span></div><div class="line">    <span class="keyword">if</span> (findViewById(R.id.detail_fragment_container) == <span class="keyword">null</span>) &#123;</div><div class="line">        Intent intent = CrimePagerActivity.newIntent(<span class="keyword">this</span>, crime.getId());</div><div class="line">        startActivity(intent);</div><div class="line">    &#125; <span class="keyword">else</span> &#123;</div><div class="line">    <span class="comment">// 否则就将 detail 页面放到 fragment 容器中</span></div><div class="line">        Fragment newDetail = CrimeFragment.newInstance(crime.getId());</div><div class="line"></div><div class="line">        getSupportFragmentManager().beginTransaction()</div><div class="line">                .replace(R.id.detail_fragment_container, newDetail)</div><div class="line">                .commit();</div><div class="line">    &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<p>在 CrimeListFragment 中持有实现接口的 activity 的引用，然后在生命周期末去除引用以便内存的回收</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// CrimeListFragment</span></div><div class="line"><span class="keyword">private</span> Callbacks mCallbacks;</div><div class="line"></div><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onAttach</span><span class="params">(Context context)</span> </span>&#123;</div><div class="line">    <span class="keyword">super</span>.onAttach(context);</div><div class="line">    mCallbacks = (Callbacks) context;</div><div class="line">&#125;</div><div class="line"></div><div class="line"><span class="comment">// 中间的函数……</span></div><div class="line"></div><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onDetach</span><span class="params">()</span> </span>&#123;</div><div class="line">    <span class="keyword">super</span>.onDetach();</div><div class="line">    mCallbacks = <span class="keyword">null</span>;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<p>最后修改 onClick 事件，调用 mCallbacks.onCrimeSelected(Crime crime) 即可。这样以后，在双版面视图中点击列表中的某一项，在详情版面中就会显示相应的信息。</p>
<p>但是有一个问题，那就是在详情页（CrimeFragment）更改信息，在列表页没有任何响应，因为 CrimeListFragment 不会暂停，所以也就不会刷新，所以下一步要在 CrimeFragment 中定义回调接口， 让托管 activity 去更新 CrimeListFragment。</p>
<h2 id="2-2-CrimeFragment-的回调接口"><a href="#2-2-CrimeFragment-的回调接口" class="headerlink" title="2.2 CrimeFragment 的回调接口"></a>2.2 CrimeFragment 的回调接口</h2><p>首先定义回调接口，这里想让托管者做的就是在 Crime 详情进行更新时更新列表</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// CrimeFragment</span></div><div class="line"><span class="keyword">private</span> Callbacks mCallbacks;</div><div class="line"></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">Callbacks</span> </span>&#123;</div><div class="line">	<span class="function"><span class="keyword">void</span> <span class="title">onCrimeUpdated</span><span class="params">(Crime crime)</span></span>;</div><div class="line">&#125;</div><div class="line"></div><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onAttach</span><span class="params">(Context context)</span> </span>&#123;</div><div class="line">    <span class="keyword">super</span>.onAttach(context);</div><div class="line">    mCallbacks = (Callbacks) context;</div><div class="line">&#125;</div><div class="line"></div><div class="line"><span class="comment">// 中间的函数……</span></div><div class="line"></div><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onDetach</span><span class="params">()</span> </span>&#123;</div><div class="line">    <span class="keyword">super</span>.onDetach();</div><div class="line">    mCallbacks = <span class="keyword">null</span>;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<p>在 CrimeListActivity 中实现该接口：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onCrimeUpdated</span><span class="params">(Crime crime)</span> </span>&#123;</div><div class="line">    CrimeListFragment listFragment = (CrimeListFragment)</div><div class="line">            getSupportFragmentManager()</div><div class="line">                    .findFragmentById(R.id.fragment_container);</div><div class="line">    listFragment.updateUI();</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<p>由于只要托管 CrimeFragment 的 activity 都应该实现其回调接口，所以在 CrimePagerActivity 中提供一个空的接口实现</p>
<p>之后在每次数据发生更改时都调用 <code>mCallbacks.onCrimeUpdated(mCrime);</code>即可。书上将更新模型层也放到了一起。</p>
<h1 id="3-挑战的后遗症：删除-Crime"><a href="#3-挑战的后遗症：删除-Crime" class="headerlink" title="3. 挑战的后遗症：删除 Crime"></a>3. 挑战的后遗症：删除 Crime</h1><p>还记得我们在 ToolBar 那一章加入的挑战吗，就是删除一个 Crime，对于 CriminalIntent 这个应用来说，双版面和单版面的删除操作应该有着不同的结果，但这些行为在书上没有定义，所以我们再自己想一种解决方案，以便确立如何写接下来的补充程序。</p>
<ol>
<li>双版面的界面下，点击删除应该要让左边的列表中去掉删除的那一项，并且详情页也要改为已存在的某一项的详情，为了方便实现，我们在这里改为已存在的第一项。如果只有最后一项并且点击了删除，那么右边应该要变成空白。</li>
<li>单版面的界面下，点击删除就直接删去该条记录，然后结束 activity。</li>
</ol>
<p>在这里我在 CrimeFragment 的 Callbacks 接口中加入了 onCrimeDelete(Crime crime) 方法与 onCrimeAllDeleted(Crime crime) 方法，在 CrimeListActivity 中实现如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div></pre></td><td class="code"><pre><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onCrimeDeleted</span><span class="params">(Crime crime)</span> </span>&#123;</div><div class="line">	<span class="comment">// 如果只是删除了一个，而还有其他的 Crime 的话，</span></div><div class="line">	<span class="comment">// 就相当于选中一个 Crime，这里传过来的应该是第一个 Crime</span></div><div class="line">    onCrimeSelected(crime);</div><div class="line">&#125;</div><div class="line"></div><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onCrimeAllDeleted</span><span class="params">(Crime crime)</span> </span>&#123;</div><div class="line">	<span class="comment">// 如果全部删除，就直接将该 fragment 移去</span></div><div class="line">    CrimeFragment fragment = (CrimeFragment)</div><div class="line">            getSupportFragmentManager()</div><div class="line">                    .findFragmentById(R.id.detail_fragment_container);</div><div class="line">    <span class="keyword">if</span> (fragment != <span class="keyword">null</span>) &#123;</div><div class="line">        getSupportFragmentManager()</div><div class="line">                .beginTransaction()</div><div class="line">                .remove(fragment)</div><div class="line">                .commit();</div><div class="line">    &#125;</div><div class="line">    <span class="comment">// 并且更新列表页</span></div><div class="line">    onCrimeUpdated(crime);</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<p>在 CrimePagerActivity 中也要实现这两个方法，但是对于这个 activity 来说只要进行 finish() 即可。</p>
<p>在删除按钮的选中监听中：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line">CrimeLab.get(getActivity()).deleteCrime(mCrime);</div><div class="line"><span class="keyword">if</span> (CrimeLab.get(getActivity()).getCrimes().isEmpty()) &#123;</div><div class="line">    mCallbacks.onCrimeAllDeleted(mCrime);</div><div class="line">&#125; <span class="keyword">else</span> &#123;</div><div class="line">    mCrime = CrimeLab.get(getActivity()).getCrimes().get(<span class="number">0</span>);</div><div class="line">    mCallbacks.onCrimeDeleted(mCrime); <span class="comment">// 这里相当于选中第一个</span></div><div class="line">    updateCrime(); <span class="comment">// 这里面升级了数据层并且更新了列表</span></div><div class="line">&#125;</div><div class="line"><span class="keyword">return</span> <span class="keyword">true</span>;</div></pre></td></tr></table></figure>
<p>整个程序就此完成啦~</p>

          
        
      
    </div>

    <div>
      
    </div>

    <div>
      
    </div>

    <div>
      
    </div>

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal " itemscope itemtype="http://schema.org/Article">
    <link itemprop="mainEntityOfPage" href="https://kniost.github.io/2017-01-12-cj0ajvouz00111js6krejdvpn/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="kniost">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/images/avatar.jpg">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="结网之路">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
            
            
              
                
                <a class="post-title-link" href="/2017-01-12-cj0ajvouz00111js6krejdvpn/" itemprop="url">
                  Android编程权威指南（第二版）学习笔记（十六）—— 第16章 使用 intent 拍照
                </a>
              
            
          </h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2017-01-12T17:55:10+08:00">
                2017-01-12
              </time>
            

            

            
          </span>

          
            <span class="post-category" >
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/Android编程权威指南学习笔记/" itemprop="url" rel="index">
                    <span itemprop="name">Android编程权威指南学习笔记</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
              <span class="post-comments-count">
                <span class="post-meta-divider">|</span>
                <span class="post-meta-item-icon">
                  <i class="fa fa-comment-o"></i>
                </span>
                <a href="/2017-01-12-cj0ajvouz00111js6krejdvpn/#comments" itemprop="discussionUrl">
                  <span class="post-comments-count disqus-comment-count" data-disqus-identifier="2017-01-12-cj0ajvouz00111js6krejdvpn/" itemprop="commentCount"></span>
                </a>
              </span>
            
          

          
          

          

          

          

        </div>
      </header>
    


    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <blockquote>
<p>本章主要讲了如何使用 intent 拍照，存储照片和展示照片</p>
<p>GitHub 地址：<br><a href="https://github.com/kniost/Android-Programming-Big-Nerd-Ranch-Guide-2nd/commit/a3abc7009d2c225159c71e6d71e351eb6284bdfb" target="_blank" rel="external">完成16章，未完成挑战</a><br><a href="https://github.com/kniost/Android-Programming-Big-Nerd-Ranch-Guide-2nd/commit/acaa89b7be9f3b7f481a5e4c190bfaf224c3e3ee" target="_blank" rel="external">完成16章挑战1</a><br><a href="https://github.com/kniost/Android-Programming-Big-Nerd-Ranch-Guide-2nd/commit/2462577c3c4b4acb614b6e01a69124d235028cef" target="_blank" rel="external">完成16章挑战2</a></p>
</blockquote>
<h1 id="1-外部存储"><a href="#1-外部存储" class="headerlink" title="1. 外部存储"></a>1. 外部存储</h1><p>相机照片动辄几 MB 大小，直接保存在数据库中肯定不现实。很自然，大家会想到直接使用设备的文件系统。<br>一般来讲，应用都应该使用私有存储空间保存各类文件。还记得吗?在前面章节中，我们在私有存储空间保存过 SQLite 数据文件。使用类似 Context.getFileStreamPath(String)和 Context.getFilesDir()这样的方法，我们也可以实现这样的存储目标，下表所示：</p>
<table>
<thead>
<tr>
<th style="text-align:left">Context 类提供的方法</th>
<th style="text-align:left">使用目的</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:left"><code>File getFilesDir()</code></td>
<td style="text-align:left">获取/data/data/<packagename>/files 目录</packagename></td>
</tr>
<tr>
<td style="text-align:left"><code>FileInputStream openFileInput(String name)</code></td>
<td style="text-align:left">打开现有文件进行读取</td>
</tr>
<tr>
<td style="text-align:left"><code>FileOutputStream openFileOutput(String name, int mode)</code></td>
<td style="text-align:left">打开文件进行写入，如不存在就创建它</td>
</tr>
<tr>
<td style="text-align:left"><code>File getDir(String name, int mode)</code></td>
<td style="text-align:left">获取/data/data/<packagename>/目录的子目录(如不存在就先创建它)</packagename></td>
</tr>
<tr>
<td style="text-align:left"><code>String[] fileList()</code></td>
<td style="text-align:left">获取/data/data/<packagename>/files 目录下的文件列表。可与其他方法配合使用，例如 openFileInput(String)</packagename></td>
</tr>
<tr>
<td style="text-align:left"><code>File getCacheDir()</code></td>
<td style="text-align:left">获取/data/data/<packagename>/cache 目录。应注意及时清理该目录，并节约使用空间</packagename></td>
</tr>
</tbody>
</table>
<p>如果想存储的文件仅供应用内部使用，使用上表中的各类方法就可以了。而如果想共享文件给其他应用或是接收其他应用的文件(如相机应用拍摄的照片)时，路只有一条:使用外部存储保存文件。<br>外部存储有两类:主外部存储和其他各类存储介质。所有的 Android 设备至少应有一个主外部存储地。使用<code>Environment.getExternalStorageDirectory()</code>可以返回这个外部存储目录。 以前，这个存储地通常是指 SD 卡，但现在都已基本整合至了设备内部。即使现在还有设备使用扩展外部存储，也应算作其他各类存储介质这一类了。<br>Context 也提供了一些访问外部存储空间要用到的方法，如下表所示。</p>
<table>
<thead>
<tr>
<th style="text-align:left">方法</th>
<th style="text-align:left">使用目的</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:left"><code>File getExternalCacheDir()</code></td>
<td style="text-align:left">获取主外部存储上的缓存文件目录。用法类似 getCacheDir()方法，但要注意，Android 一般不会自动清理该目录</td>
</tr>
<tr>
<td style="text-align:left"><code>File[] getExternalCacheDirs()</code></td>
<td style="text-align:left">获取多个外部存储上的缓存文件目录</td>
</tr>
<tr>
<td style="text-align:left"><code>File getExternalFilesDir(String)</code></td>
<td style="text-align:left">获取主外部存储上存放常规文件的文件目录。通过 String 参数，可访问特定内容类型的子目录。内容类型常量以 DIRECTORY<em>为前缀，定义在 Environment 中 。 例如 , 用于 图像 文件 的 Environment.DIRECTORY</em> PICTURES</td>
</tr>
<tr>
<td style="text-align:left"><code>File[] getExternalFilesDirs(String)</code></td>
<td style="text-align:left">类似 getExternalFilesDir(String)方法，但该方法可获取指定类型的所有文件目录</td>
</tr>
<tr>
<td style="text-align:left"><code>File[] getExternalMediaDirs()</code></td>
<td style="text-align:left">获取 Android 存储图片、视频和音乐文件的所有外部文件目录。和 getExternalFilesDir(Environment.DIRECTORY_PICTURES) 方法 区别 在于，调用该方法，多媒体扫描器会自动扫描目标目录，并将存放的多媒体文件暴露给能够播放音乐、浏览视频和图片的应用。也就是说， getExternalMediaDirs()方法返回目录中存放的任何文件都会自动出现在多媒体应用中</td>
</tr>
</tbody>
</table>
<h2 id="1-1-指定照片存放位置"><a href="#1-1-指定照片存放位置" class="headerlink" title="1.1 指定照片存放位置"></a>1.1 指定照片存放位置</h2><p>首先，一张照片的文件名我们用一个 Crime 的 ID 来标识，所以在 Crime.java 中加入了获取文件名的方法：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> String <span class="title">getPhotoFileName</span><span class="params">()</span> </span>&#123;</div><div class="line">	<span class="keyword">return</span> <span class="string">"IMG_"</span> + getId().toString() + <span class="string">".jpg"</span>;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<p>然后在 CrimeLab.java 中加入获取路径文件的函数：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> File <span class="title">getPhotoFile</span><span class="params">(Crime crime)</span> </span>&#123;</div><div class="line">    File externalFilesDir = mContext</div><div class="line">            .getExternalFilesDir(Environment.DIRECTORY_PICTURES);</div><div class="line"></div><div class="line">    <span class="keyword">if</span> (externalFilesDir == <span class="keyword">null</span>) &#123;</div><div class="line">        <span class="keyword">return</span> <span class="keyword">null</span>;</div><div class="line">    &#125;</div><div class="line"></div><div class="line">    <span class="keyword">return</span> <span class="keyword">new</span> File(externalFilesDir, crime.getPhotoFileName());</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<h2 id="1-2-外部存储使用权限"><a href="#1-2-外部存储使用权限" class="headerlink" title="1.2 外部存储使用权限"></a>1.2 外部存储使用权限</h2><p>读写外部存储需要获得权限，一般在<code>AndroidManifest.xml</code>中使用<code>&lt;uses-permission&gt;</code>标签来使用。而对于 API 19（Android 4.4）及以后的新版系统来说，应用不需要再申请 <code>Context.getExternalFilesDir(String)</code> 所需要的权限了，所以这个权限申请是这么写的：</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="tag">&lt;<span class="name">uses-permission</span></span></div><div class="line">    <span class="attr">android:name</span>=<span class="string">"android.permission.READ_EXTERNAL_STORAGE"</span></div><div class="line">    <span class="attr">android:maxSdkVersion</span>=<span class="string">"18"</span>/&gt;</div></pre></td></tr></table></figure>
<h1 id="2-使用相机-intent"><a href="#2-使用相机-intent" class="headerlink" title="2. 使用相机 intent"></a>2. 使用相机 intent</h1><p>实现拍照功能只需要使用一个隐式 intent，分为下面几步：</p>
<ul>
<li>获取保存图片的文件存储位置</li>
<li>处理拍照按钮，实现触发拍照，其实就是发送一个带有 <code>MediaStore.ACTION_IMAGE_CAPTURE</code>的 intent 即可。</li>
</ul>
<p>对于 intent 的操作，我们需要定义在 MediaStore 类中的<code>ACTION_CAPTURE_IMAGE</code>。MediaStore 类定义了一些公共接口，可用于处理图像、视频以及音乐这些常见的多媒体任务。当然，这也包括触发相机应用的拍照 intent。</p>
<p>如果只用<code>ACTION_IMAGE_CAPTURE</code>打开相机应用，默认只能拍摄缩略图这样的低分辨率照片，而且照片会保存在 onActivityResult(…)返回的 Intent 对象里。要想获得全尺寸照片，就要让它使用文件系统存储照片。这可以通过传入保存在 <code>MediaStore.EXTRA_OUTPUT</code> 中的指向存储路径的 Uri 来完成。<br>  编写用于拍照的隐式 intent，拍摄的照片应该保存在 mPhotoFile 指定的地方。同时，别忘了检查设备上是否安装有相机应用，以及是否有地方存储照片。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div></pre></td><td class="code"><pre><div class="line">mPhotoButton = (ImageButton) v.findViewById(R.id.crime_camera);</div><div class="line"><span class="comment">// 首先创建一个用于拍照的 Intent 对象</span></div><div class="line"><span class="keyword">final</span> Intent captureImage = <span class="keyword">new</span> Intent(MediaStore.ACTION_IMAGE_CAPTURE);</div><div class="line"><span class="comment">// 检查是否有可拍照的应用</span></div><div class="line"><span class="keyword">boolean</span> canTakePhoto = mPhotoFile != <span class="keyword">null</span> &amp;&amp;</div><div class="line">        captureImage.resolveActivity(packageManager) != <span class="keyword">null</span>;</div><div class="line">mPhotoButton.setEnabled(canTakePhoto);</div><div class="line"></div><div class="line"><span class="keyword">if</span> (canTakePhoto) &#123;</div><div class="line">	<span class="comment">// 建立访问照片目录的 Uri</span></div><div class="line">    Uri uri = Uri.fromFile(mPhotoFile);</div><div class="line">    <span class="comment">// 将该 Uri 放入 intent 对象中</span></div><div class="line">    captureImage.putExtra(MediaStore.EXTRA_OUTPUT, uri);</div><div class="line">&#125;</div><div class="line"></div><div class="line">mPhotoButton.setOnClickListener(<span class="keyword">new</span> View.OnClickListener() &#123;</div><div class="line">    <span class="meta">@Override</span></div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onClick</span><span class="params">(View view)</span> </span>&#123;</div><div class="line">    	<span class="comment">// 使用 startActivityForResult 是为了拍完照后刷新视图</span></div><div class="line">        startActivityForResult(captureImage, REQUEST_PHOTO);</div><div class="line">    &#125;</div><div class="line">&#125;);</div></pre></td></tr></table></figure>
<h1 id="3-缩放和显示位图"><a href="#3-缩放和显示位图" class="headerlink" title="3. 缩放和显示位图"></a>3. 缩放和显示位图</h1><p>有了照片，接下来就是找到并加载它，然后展示给用户看。在技术实现上，这需要加载照片到大小合适的 Bitmap 对象中。而要从文件生成 Bitmap 对象，我们需要 BitmapFactory 类:<br>    <code>Bitmap bitmap = BitmapFactory.decodeFile(mPhotoFile.getPath());</code></p>
<p>Bitmap 是个简单对象，它只存储实际像素数据。也就是说，即使原始照片已压缩过，但存入 Bitmap 对象时，文件并不会同样压缩。因此，如果有一个16万像素24位已压缩为5Mb 大小的 JPG 照片文件，一旦载入 Bitmap 对象，就会立即膨胀至48Mb 大小!<br>这个问题可以设法解决，但需要手工缩放位图照片。具体做法就是，首先确认文件到底有多大，然后考虑按照给定区域大小合理缩放文件。最后，重新读取缩放后的文件，创建 Bitmap 对象。<br>既然需要处理图像文件，我们建立一个通用的工具类，名为 PictureUtils.java。在其中添加 getScaledBitmap(String, int, int)缩放方法，</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">PictureUtils</span> </span>&#123;</div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Bitmap <span class="title">getScaledBitmap</span><span class="params">(String path, <span class="keyword">int</span> destWidth, <span class="keyword">int</span> destHeight)</span> </span>&#123;</div><div class="line">        <span class="comment">// Read in the dimensions of the image on disk</span></div><div class="line">        BitmapFactory.Options options = <span class="keyword">new</span> BitmapFactory.Options();</div><div class="line">        options.inJustDecodeBounds = <span class="keyword">true</span>;</div><div class="line">        BitmapFactory.decodeFile(path, options);</div><div class="line"></div><div class="line">        <span class="keyword">float</span> srcWidth = options.outWidth;</div><div class="line">        <span class="keyword">float</span> srcHeight = options.outHeight;</div><div class="line"></div><div class="line">        <span class="comment">// Figure out how much to scale down by</span></div><div class="line">        <span class="keyword">int</span> inSampleSize = <span class="number">1</span>;</div><div class="line">        <span class="keyword">if</span> (srcHeight &gt; destHeight || srcWidth &gt; destWidth) &#123;</div><div class="line">            <span class="keyword">if</span> (srcWidth &gt; srcHeight) &#123;</div><div class="line">                inSampleSize = Math.round(srcHeight / destHeight);</div><div class="line">            &#125; <span class="keyword">else</span> &#123;</div><div class="line">                inSampleSize = Math.round(srcWidth / destWidth);</div><div class="line">            &#125;</div><div class="line">        &#125;</div><div class="line"></div><div class="line">        options = <span class="keyword">new</span> BitmapFactory.Options();</div><div class="line">        options.inSampleSize = inSampleSize;</div><div class="line"></div><div class="line">        <span class="comment">// Read in and create final bitmap</span></div><div class="line">        <span class="keyword">return</span> BitmapFactory.decodeFile(path, options);</div><div class="line">    &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<p>上述方法中，inSampleSize 值很关键。它决定着缩略图像素的大小。假设这个值是1的话，就表明缩略图和原始照片的水平像素大小一样。如果是2的话，它们的水平像素比就是1∶2。因此，inSampleSize 值为2时，缩略图的像素数就是原始文件的四分之一。<br>问题总是接踵而来。解决了缩放问题，又冒出了新问题:fragment 刚启动时，PhotoView 究竟有多大无人知道。onCreate(…)、onStart()和 onResume()方法启动后，才会有首个实例化布局出现。也就在此时，显示在屏幕上的视图才会有大小尺寸。这也是出现新问题的原因。<br>解决方案有两个:要么等布局实例化完成并显示，要么干脆使用保守估算值。特定条件下， 尽管估算比较主观，但确实是一个切实可行的办法。再添加一个 getScaledBitmap(String, Activity)静态 Bitmap 估算方法。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Bitmap <span class="title">getScaledBitmap</span><span class="params">(String path, Activity activity)</span> </span>&#123;</div><div class="line">    Point size = <span class="keyword">new</span> Point();</div><div class="line">    activity.getWindowManager().getDefaultDisplay()</div><div class="line">            .getSize(size);</div><div class="line"></div><div class="line">    <span class="keyword">return</span> getScaledBitmap(path, size.x, size.y);</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<h1 id="4-功能声明"><a href="#4-功能声明" class="headerlink" title="4. 功能声明"></a>4. 功能声明</h1><p>应用的拍照功能用起来不错，但还有件事情要做:告诉目标用户应用具有拍照功能。</p>
<p>假如应用要用到诸如相机、NFC，或者任何其他的随设备走的功能时，都应该要让 Android 系统知道。否则，假如设备缺少这样的功能，类似 Google Play 商店的安装程序就会拒绝安装应用。<br>为声明需要使用相机，在 AndroidManifest.xml 中加入<code>&lt;uses-feature&gt;</code>标签：</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line"><span class="tag">&lt;<span class="name">uses-feature</span></span></div><div class="line">    <span class="attr">android:name</span>=<span class="string">"android.hardware.camera2"</span></div><div class="line">    <span class="attr">android:required</span>=<span class="string">"false"</span>/&gt;</div></pre></td></tr></table></figure>
<h1 id="5-布局文件中的-lt-include-gt-标签"><a href="#5-布局文件中的-lt-include-gt-标签" class="headerlink" title="5. 布局文件中的 &lt;include&gt; 标签"></a>5. 布局文件中的 <code>&lt;include&gt;</code> 标签</h1><p>如果有重复的布局可以使用，那么可以采用 include 标签，直接在不同的 layout 中引用。<br>然而，经验表明，布局文件的优点是可靠又好用。例如，直接查看布局文件内容，就可以快速准确地知道应用视图是如何构建的。然而，一旦用了 include 标签，一切就不好说了。还想明白视图构成的话，就得仔细翻看布局主文件以及所有 include 的布局文件。这种非直观的感觉，极易让人失去耐心。<br>用户界面是应用改动相对频繁的部分。既然这样，不顾一切地追求复用原则很可能会适得其反。因此，在视图层开发时，我们一定要多多考量，尽量做到审慎、合理地使用 include 标签。</p>
<h1 id="6-挑战练习"><a href="#6-挑战练习" class="headerlink" title="6. 挑战练习"></a>6. 挑战练习</h1><h2 id="6-1-优化照片显示"><a href="#6-1-优化照片显示" class="headerlink" title="6.1 优化照片显示"></a>6.1 优化照片显示</h2><p>新建一个 GlancePictureFragment，继承自 DialogFragment，代码如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div><div class="line">42</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">GlancePictureFragment</span> <span class="keyword">extends</span> <span class="title">DialogFragment</span> </span>&#123;</div><div class="line"></div><div class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String ARG_PATH = <span class="string">"path"</span>;</div><div class="line"></div><div class="line">    <span class="keyword">private</span> ImageView mImage;</div><div class="line"></div><div class="line">	<span class="comment">// 由于文件比较大，所以将文件路径传入即可</span></div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> GlancePictureFragment <span class="title">newInstance</span><span class="params">(String path)</span> </span>&#123;</div><div class="line">        Bundle args = <span class="keyword">new</span> Bundle();</div><div class="line">        args.putString(ARG_PATH, path);</div><div class="line">        GlancePictureFragment fragment = <span class="keyword">new</span> GlancePictureFragment();</div><div class="line">        fragment.setArguments(args);</div><div class="line">        <span class="keyword">return</span> fragment;</div><div class="line">    &#125;</div><div class="line"></div><div class="line">    <span class="meta">@NonNull</span></div><div class="line">    <span class="meta">@Override</span></div><div class="line">    <span class="function"><span class="keyword">public</span> Dialog <span class="title">onCreateDialog</span><span class="params">(Bundle savedInstanceState)</span> </span>&#123;</div><div class="line">		<span class="comment">// 使用 getArguments() 方法取出照片文件路径</span></div><div class="line">        String path = getArguments().getString(ARG_PATH);</div><div class="line"></div><div class="line">		<span class="comment">// 这个新的 style 其实就做了一件事，那就是使窗口全屏</span></div><div class="line">		<span class="comment">// 注意如果继承了 @android:Theme.Dialog 的话，窗口</span></div><div class="line">		<span class="comment">// 大小就限定了，所以我没有继承</span></div><div class="line">        <span class="keyword">final</span> Dialog dialog = <span class="keyword">new</span> Dialog(getActivity(), R.style.CustomDialogTheme);</div><div class="line">        <span class="comment">// 这个 layout 中只有一个 ImageView</span></div><div class="line">        dialog.setContentView(R.layout.dialog_image_glance);</div><div class="line"></div><div class="line">        mImage = (ImageView) dialog.findViewById(R.id.glance_image);</div><div class="line">        <span class="comment">// 仍然使用 PictureUtils 类的工具来获得缩放的 Bitmap</span></div><div class="line">        mImage.setImageBitmap(</div><div class="line">                PictureUtils.getScaledBitmap(path, getActivity()));</div><div class="line">        mImage.setOnClickListener(<span class="keyword">new</span> View.OnClickListener() &#123;</div><div class="line">            <span class="meta">@Override</span></div><div class="line">            <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onClick</span><span class="params">(View v)</span> </span>&#123;</div><div class="line">            <span class="comment">// 点击图片则退出该 dialog</span></div><div class="line">                dialog.dismiss();</div><div class="line">            &#125;</div><div class="line">        &#125;);</div><div class="line">        <span class="keyword">return</span> dialog;</div><div class="line">    &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<p>然后在图片的点击事件中声明即可</p>
<h2 id="6-2-优化缩略图加载"><a href="#6-2-优化缩略图加载" class="headerlink" title="6.2 优化缩略图加载"></a>6.2 优化缩略图加载</h2><p>首先修改更新视图的函数，接受高宽的指定像素：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">updatePhotoView</span><span class="params">(<span class="keyword">int</span> width, <span class="keyword">int</span> height)</span> </span>&#123;</div><div class="line">    <span class="keyword">if</span> (mPhotoFile == <span class="keyword">null</span> || !mPhotoFile.exists()) &#123;</div><div class="line">        mPhotoView.setImageDrawable(<span class="keyword">null</span>);</div><div class="line">    &#125; <span class="keyword">else</span> &#123;</div><div class="line">        Bitmap bitmap = PictureUtils.getScaledBitmap(</div><div class="line">                mPhotoFile.getPath(), width, height);</div><div class="line">        mPhotoView.setImageBitmap(bitmap);</div><div class="line">    &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<p>之后，先获取 mPhotoView 的 ViewTreeObserver，然后设置 OnGlobalLayoutListener 监听器，在监听器中即可获取视图的高度和宽度，然后进行图片显示。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line">mPhotoObserver = mPhotoView.getViewTreeObserver();</div><div class="line">mPhotoObserver.addOnGlobalLayoutListener(</div><div class="line">        <span class="keyword">new</span> ViewTreeObserver.OnGlobalLayoutListener() &#123;</div><div class="line">    <span class="meta">@Override</span></div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onGlobalLayout</span><span class="params">()</span> </span>&#123;</div><div class="line">        updatePhotoView(</div><div class="line">                mPhotoView.getWidth(),</div><div class="line">                mPhotoView.getHeight());</div><div class="line">        Log.i(<span class="string">"CrimeFragment"</span>, <span class="string">"onGlobalLayout: Observed"</span>);</div><div class="line">    &#125;</div><div class="line">&#125;);</div></pre></td></tr></table></figure>
          
        
      
    </div>

    <div>
      
    </div>

    <div>
      
    </div>

    <div>
      
    </div>

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal " itemscope itemtype="http://schema.org/Article">
    <link itemprop="mainEntityOfPage" href="https://kniost.github.io/2017-01-11-cj0ajvouw000u1js6vncmebx5/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="kniost">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/images/avatar.jpg">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="结网之路">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
            
            
              
                
                <a class="post-title-link" href="/2017-01-11-cj0ajvouw000u1js6vncmebx5/" itemprop="url">
                  Android编程权威指南（第二版）学习笔记（十五）—— 第15章 隐式 Intent
                </a>
              
            
          </h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2017-01-11T20:45:10+08:00">
                2017-01-11
              </time>
            

            

            
          </span>

          
            <span class="post-category" >
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/Android编程权威指南学习笔记/" itemprop="url" rel="index">
                    <span itemprop="name">Android编程权威指南学习笔记</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
              <span class="post-comments-count">
                <span class="post-meta-divider">|</span>
                <span class="post-meta-item-icon">
                  <i class="fa fa-comment-o"></i>
                </span>
                <a href="/2017-01-11-cj0ajvouw000u1js6vncmebx5/#comments" itemprop="discussionUrl">
                  <span class="post-comments-count disqus-comment-count" data-disqus-identifier="2017-01-11-cj0ajvouw000u1js6vncmebx5/" itemprop="commentCount"></span>
                </a>
              </span>
            
          

          
          

          

          

          

        </div>
      </header>
    


    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <blockquote>
<p>本章主要介绍了如何使用隐式 Intent 启动其他应用的 Activity，以及从用隐式 Intent 的方法启动的应用中获取数据</p>
<p>GitHub 地址：<br><a href="https://github.com/kniost/Android-Programming-Big-Nerd-Ranch-Guide-2nd/commit/346d4aeceb125c5e52164af14afe023ecd713108" target="_blank" rel="external">完成15章但未完成挑战</a><br><a href="https://github.com/kniost/Android-Programming-Big-Nerd-Ranch-Guide-2nd/commit/c6641fc4fa5075bcc1d34d8c4a64f9d32f3ccd56" target="_blank" rel="external">完成15章挑战1</a><br><a href="https://github.com/kniost/Android-Programming-Big-Nerd-Ranch-Guide-2nd/commit/e24f4c1b65bd36a475b038cf051c3c5033c7a967" target="_blank" rel="external">完成15章挑战2</a></p>
</blockquote>
<h1 id="1-使用格式化字符串"><a href="#1-使用格式化字符串" class="headerlink" title="1. 使用格式化字符串"></a>1. 使用格式化字符串</h1><p>在<code>string.xml</code>中定义字符串时，我们可以先用占位符代替一些内容，然后在引用的时候用相应的字符串代替占位符，得到所需要的字符串。<br>比如一个定义好的字符串：</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="tag">&lt;<span class="name">string</span> <span class="attr">name</span>=<span class="string">"string_name"</span>&gt;</span>我的名字叫%1$s，我有%2$d个苹果<span class="tag">&lt;/<span class="name">string</span>&gt;</span></div></pre></td></tr></table></figure>
<p>引用时可以这样：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line">String s = getResources()</div><div class="line">	.getString(R.string.string_name, <span class="string">"王大宝"</span>, <span class="number">512</span>);</div><div class="line"><span class="comment">// 结果应该是 s == "我的名字叫王大宝，我有512个苹果"</span></div></pre></td></tr></table></figure>
<p>这种字符串资源用作模板再方便不过了。</p>
<h1 id="2-使用隐式-intent"><a href="#2-使用隐式-intent" class="headerlink" title="2. 使用隐式 intent"></a>2. 使用隐式 intent</h1><p>Intent 对象用来向操作系统说明需要处理的任务。使用显式 intent，我们需指定要操作系统 启动哪个 activity。而使用隐式 intent，只需告诉操作系统我们想要做什么，操作系统就会去启动能够胜任工作任务的 activity。如果找到多个符合的 activity，用户会看到一个可选应用列表，然后就看用户如何选择了。</p>
<h2 id="2-1-隐式-intent-的组成"><a href="#2-1-隐式-intent-的组成" class="headerlink" title="2.1 隐式 intent 的组成"></a>2.1 隐式 intent 的组成</h2><p>下面是一个隐式 intent 的主要组成部分：</p>
<ol>
<li><strong>要执行的操作</strong>。通常以 Intent 类中的常量来表示。例如，要访问查看某个 URL，可以使用 Intent.ACTION_VIEW;要发送邮件，可以使用 Intent.ACTION_SEND。</li>
<li><strong>要访问数据的位置</strong>。这可能是设备以外的资源，如某个网页的 URL，也可能是指向某个文件的 URI，或者是指向 ContentProvider 中某条记录的某个内容 URI(content URI)。</li>
<li><strong>操作涉及的数据类型</strong>。 这指的是 MIME 形式的数据类型，如<code>text/html</code>或<code>audio/mpeg3</code>。如果一个 intent 包含数据位置，那么通常可以从中推测出数据的类型。 </li>
<li><strong>可选类别</strong>。如果操作用于描述具体要做什么，那么类别通常用来描述我们是何时、何地或者如何使用某 个 activity 的。例如，Android 的<code>android.intent.category.LAUNCHER</code>类别表明，activity 应该显示在顶级应用启动器中;而<code>android.intent.category.INFO</code>类别表明，虽然 activity 向用户显示了包信息，但它不应该显示在启动器中。</li>
</ol>
<p>和显式 intent 一样，隐式 intent 也可以包含 extra 信息。不过，操作系统在寻找适用的 activity 时， 不会使用附加在隐式 intent 上的任何 extra。<br>注意，显式 intent 也可以使用隐式 intent 的操作和数据部分。这相当于要求特定 activity 去处理特定任务。</p>
<h2 id="2-1-发送消息"><a href="#2-1-发送消息" class="headerlink" title="2.1 发送消息"></a>2.1 发送消息</h2><p>如果我们要发送一段文字消息，应该创建一个用于发送文字消息的 intent：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// 首先指定动作为 Intent.ACTION_SEND</span></div><div class="line">Intent i = <span class="keyword">new</span> Intent(Intent.ACTION_SEND);</div><div class="line"><span class="comment">// 设置文字类别，这里是纯文本</span></div><div class="line">i.setType(<span class="string">"text/plain"</span>);</div><div class="line"><span class="comment">// 设置文字内容</span></div><div class="line">i.putExtra(Intent.EXTRA_TEXT, getCrimeReport());</div><div class="line"><span class="comment">// 设置主题内容</span></div><div class="line">i.putExtra(Intent.EXTRA_SUBJECT,</div><div class="line">        getString(R.string.crime_report_subject));</div><div class="line"><span class="comment">// 每一次都会弹出选择发送的应用弹框</span></div><div class="line">i = Intent.createChooser(i, getString(R.string.send_report));</div><div class="line">startActivity(i);</div></pre></td></tr></table></figure>
<p>没错，使用一个隐式的 intent 就是这么简单，对于不同的需求，查询文档，使用相应的方法调用隐式 intent 就可以啦。</p>
<h2 id="2-2-获取联系人信息"><a href="#2-2-获取联系人信息" class="headerlink" title="2.2 获取联系人信息"></a>2.2 获取联系人信息</h2><p>现在，创建另一个隐式 intent，实现让用户从联系人应用里选择嫌疑人。新建的隐式 intent 将 由操作以及数据获取位置组成。</p>
<ul>
<li>操作为<code>Intent.ACTION_PICK</code>。</li>
<li>联系人数据获取位置为 <code>ContactsContract.Contacts.CONTENT_URI</code>。</li>
</ul>
<p>简而言之，就是请 Android 帮忙从联系人数据库里获取某个具体联系人。<br>因为要获取启动 activity 的返回结果，我们调用 startActivityForResult(…)方法并传 入 intent 和 RequestCode。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> REQUEST_CONTACT = <span class="number">1</span>;</div><div class="line"></div><div class="line">……</div><div class="line"></div><div class="line">Intent pickContact = <span class="keyword">new</span> Intent(Intent.ACTION_PICK, </div><div class="line">	ContactsContract.Contacts.CONTENT_URI);</div><div class="line">startActivityForResult(pickContact, REQUEST_CONTACT);</div></pre></td></tr></table></figure>
<h3 id="2-2-1-从联系人列表中获取联系人数据"><a href="#2-2-1-从联系人列表中获取联系人数据" class="headerlink" title="2.2.1 从联系人列表中获取联系人数据"></a>2.2.1 从联系人列表中获取联系人数据</h3><p>现在，我们需要从联系人应用中获取返回结果。很多应用都会共享联系人信息，因此 Android 提供了一个深度定制的 API 用于处理联系人信息，这主要是通过 ContentProvider 类来实现的。 该类的实例封装了联系人数据库并提供给其他应用使用。</p>
<p>我们可以通过一个 ContentResolver 访问 ContentProvider。<br>前面，我们以 ACTION_PICK 启动了 activity 并要求返回结果，因此调用 onActivityResult (…)方法会接收到一个 intent。该 intent 包括了数据 URI。这个 URI 是个数据定位符，指向用户所选联系人。</p><p id="inline_code"></p><p></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div></pre></td><td class="code"><pre><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onActivityResult</span><span class="params">(<span class="keyword">int</span> requestCode, <span class="keyword">int</span> resultCode, Intent data)</span> </span>&#123;</div><div class="line">    <span class="keyword">if</span> (resultCode != Activity.RESULT_OK) &#123;</div><div class="line">        <span class="keyword">return</span>;</div><div class="line">    &#125;</div><div class="line"></div><div class="line">    <span class="keyword">if</span> (requestCode == REQUEST_CONTACT &amp;&amp; data != <span class="keyword">null</span>) &#123;</div><div class="line">        Uri contactUri = data.getData();</div><div class="line">        <span class="comment">// Specify which fields you want your query to return</span></div><div class="line">        <span class="comment">// values for. 就相当于要请求的数据域</span></div><div class="line">        String [] queryFields = <span class="keyword">new</span> String[] &#123;</div><div class="line">                ContactsContract.Contacts._ID,</div><div class="line">                ContactsContract.Contacts.DISPLAY_NAME</div><div class="line">        &#125;;</div><div class="line">        <span class="comment">// Perform your query - the contactUri is like a "where"</span></div><div class="line">        <span class="comment">// clause here 相当于一个查询数据库的语句</span></div><div class="line">        <span class="comment">// 首先 contactUri 定位了数据库的表，queryFields 定义了要查的语句</span></div><div class="line">        Cursor c = getActivity().getContentResolver()</div><div class="line">                .query(contactUri, queryFields, <span class="keyword">null</span>, <span class="keyword">null</span>, <span class="keyword">null</span>);</div><div class="line">        <span class="keyword">try</span> &#123;</div><div class="line">            <span class="comment">// Double-check that you actually got results</span></div><div class="line">            <span class="keyword">if</span> (c.getCount() == <span class="number">0</span>) &#123;</div><div class="line">                <span class="keyword">return</span>;</div><div class="line">            &#125;</div><div class="line"></div><div class="line">            <span class="comment">// Pull out the first column of the first row of data -</span></div><div class="line">            <span class="comment">// that is your suspect's name.</span></div><div class="line">            c.moveToFirst();</div><div class="line">            <span class="comment">// 获得嫌疑人的名字</span></div><div class="line">            String suspect = c.getString(</div><div class="line">                    c.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));</div><div class="line">            <span class="comment">// 获得嫌疑人的 ID</span></div><div class="line">            String suspectId = c.getString(</div><div class="line">                    c.getColumnIndex(ContactsContract.Contacts._ID));</div><div class="line">        &#125; <span class="keyword">finally</span> &#123;</div><div class="line">            <span class="comment">// 最后不要忘了关闭 cursor</span></div><div class="line">            c.close();</div><div class="line">        &#125;</div><div class="line">    &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<h3 id="2-2-2-联系人信息的使用权限"><a href="#2-2-2-联系人信息的使用权限" class="headerlink" title="2.2.2 联系人信息的使用权限"></a>2.2.2 联系人信息的使用权限</h3><p>我们如何获得读取联系人数据库的权限呢?实际上，这是联系人应用将其权限临时赋给了我们。联系人应用具有使用联系人数据库的全部权限。</p>
<p>联系人应用返回包含在 intent 中的 URI 数据给父 activity 时 , 会添加一个 <code>Intent.FLAG_GRANT_READ_URI_PERMISSION</code> 标志。该标志告诉 <code>Android,CriminalIntent</code> 应用中的父 activity 可以使用联系人数据一次。这很有用，因为不需要访问整个联系人数据库，我们只需要访问其中的一条联系人信息。</p>
<h3 id="2-2-3-检查可响应任务的-activity"><a href="#2-2-3-检查可响应任务的-activity" class="headerlink" title="2.2.3 检查可响应任务的 activity"></a>2.2.3 检查可响应任务的 activity</h3><p>有些设备上根本就没有联系人应用，这时如果操作系统找不到匹配的 activity，应用就会崩溃！<br>解决办法是在调用最开始的时候通过操作系统中的 PackageManager 类进行自检，然后决定是否禁用这个按钮。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line">PackageManager packageManager = getActivity().getPackageManager();</div><div class="line">Intent pickContact = <span class="keyword">new</span> Intent(Intent.ACTION_PICK, </div><div class="line">	ContactsContract.Contacts.CONTENT_URI);</div><div class="line"><span class="keyword">if</span> (packageManager.resolveActivity(pickContact,</div><div class="line">        PackageManager.MATCH_DEFAULT_ONLY) == <span class="keyword">null</span>) &#123;</div><div class="line">    mSuspectButton.setEnabled(<span class="keyword">false</span>);</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<p>Android 设备上安装了哪些组件以及包括哪些 activity,PackageManager 类全都知道。调用<code>resolveActivity(Intent, int)</code>方法，我们可以找到匹配给定 Intent 任务的 activity。flag 标志<code>MATCH_DEFAULT_ONLY</code>限定只搜索带<code>CATEGORY_DEFAULT</code>标志的 activity。这和 startActivity(Intent)方法类似。</p>
<h1 id="3-挑战联系"><a href="#3-挑战联系" class="headerlink" title="3. 挑战联系"></a>3. 挑战联系</h1><h2 id="3-1-ShareCompat"><a href="#3-1-ShareCompat" class="headerlink" title="3.1 ShareCompat"></a>3.1 ShareCompat</h2><p>使用 ShareCompat.IntentBuilder 内部类用于创建发送消息的 Intent。<br>将原来的代码替换如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div></pre></td><td class="code"><pre><div class="line">ShareCompat.IntentBuilder.from(getActivity())</div><div class="line">        .setType(<span class="string">"text/plain"</span>)</div><div class="line">        .setSubject(getString(R.string.crime_report_subject))</div><div class="line">        .setText(getCrimeReport())</div><div class="line">        .setChooserTitle(R.string.send_report)</div><div class="line">        .startChooser();</div><div class="line"><span class="comment">/*  Intent i = new Intent(Intent.ACTION_SEND);</span></div><div class="line">  i.setType("text/plain");</div><div class="line">  i.putExtra(Intent.EXTRA_TEXT, getCrimeReport());</div><div class="line">  i.putExtra(Intent.EXTRA_SUBJECT,</div><div class="line">          getString(R.string.crime_report_subject));</div><div class="line">  i = Intent.createChooser(i, getString(R.string.send_report));</div><div class="line">  startActivity(i);*/</div></pre></td></tr></table></figure>
<h2 id="3-2-又一个隐式-Intent"><a href="#3-2-又一个隐式-Intent" class="headerlink" title="3.2 又一个隐式 Intent"></a>3.2 又一个隐式 Intent</h2><p>这个问题分三步解决：</p>
<ol>
<li>获取选择的联系人的 _ID （在 ContactsContract.Contacts 表中）</li>
<li>通过获取到的联系人 ID 在 CommonDataKinds.Phone 表中查询联系人的电话</li>
<li>通过电话创建隐式 Intent</li>
</ol>
<p>在开始之前，数据库中要加入 suspect 的 PhoneNumber 这一列，按照之前的步骤更改 Model 层和数据库建表语句以及数据库操作函数。</p>
<p>然后要加入一个拨打电话的按钮，我的想法是，在选择联系人之前或者联系人没有电话的时候，这个按钮是不能点击的，并会显示相应的文字。</p>
<h3 id="3-2-1-获取联系人-ID"><a href="#3-2-1-获取联系人-ID" class="headerlink" title="3.2.1 获取联系人 ID"></a>3.2.1 获取联系人 ID</h3><p>在按下选择联系人的那一步的 queryField 里面加入请求联系人 Id 的列即可。最后再从 Cursor 中取出。代码在 <a href="#inline_code">2.2.1节</a></p>
<h3 id="3-2-2-通过获取到的联系人-ID-查询电话"><a href="#3-2-2-通过获取到的联系人-ID-查询电话" class="headerlink" title="3.2.2 通过获取到的联系人 ID 查询电话"></a>3.2.2 通过获取到的联系人 ID 查询电话</h3><ol>
<li><p>首先要在 AndroidManifest.xml 中，<application> 标签前面加上申请权限的标签</application></p>
 <figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="tag">&lt;<span class="name">uses-permission</span> <span class="attr">android:name</span>=<span class="string">"android.permission.READ_CONTACTS"</span>/&gt;</span></div></pre></td></tr></table></figure>
<blockquote>
<p><strong>注意这里有个坑在</strong>，由于 Android 6.0 新加入了运行时权限，所有 targetAPI 为23及以上的应用都需要在使用时申请权限，如果不加入申请权限的代码，应用将崩溃。为了节约时间和代码量，我在完成这个挑战的时候将 Project 的 targetAPI 设为22以避免这种情况的发生。</p>
</blockquote>
</li>
<li><p>在获取到 suspect 的 ID 的之后，通过 ID 查询其第一个电话号码，我写了一个方法用于获取</p>
 <figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">private</span> String <span class="title">getPhoneNumberById</span><span class="params">(String contactId)</span> </span>&#123;</div><div class="line">	<span class="comment">// 首先找到需要查找的表的 URI</span></div><div class="line">      Uri phoneUri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;</div><div class="line">      <span class="comment">// 然后还是通过 ContentResolver 对数据进行请求</span></div><div class="line">      <span class="comment">// 跟数据库的操作十分类似</span></div><div class="line">      Cursor c = getActivity().getContentResolver().query(</div><div class="line">              phoneUri,</div><div class="line">              <span class="comment">// 要请求的数据是 NUMBER，即电话号码</span></div><div class="line">              <span class="keyword">new</span> String[] &#123; ContactsContract.CommonDataKinds.Phone.NUMBER &#125;,</div><div class="line">              <span class="comment">// 相当于 Where 语句，注意这里不是 _ID，应该是 CONTACT_ID</span></div><div class="line">              <span class="comment">// 对于 Phone 表来说，CONTACT_ID 应该是其外键，_ID 是主键</span></div><div class="line">              ContactsContract.CommonDataKinds.Phone.CONTACT_ID + <span class="string">"= ? "</span>,</div><div class="line">              <span class="keyword">new</span> String[] &#123; contactId &#125;,</div><div class="line">              <span class="keyword">null</span></div><div class="line">      );</div><div class="line">      String phoneNumber = <span class="keyword">null</span>;</div><div class="line">      <span class="keyword">try</span> &#123;</div><div class="line">          <span class="keyword">if</span> (c.getCount() == <span class="number">0</span>) &#123;</div><div class="line">              <span class="keyword">return</span> phoneNumber;</div><div class="line">          &#125;</div><div class="line">          <span class="comment">// 只取第一个数据</span></div><div class="line">          c.moveToFirst();</div><div class="line">          phoneNumber = c.getString(<span class="number">0</span>);</div><div class="line">      &#125; <span class="keyword">finally</span> &#123;</div><div class="line">          c.close();</div><div class="line">      &#125;</div><div class="line">      <span class="keyword">return</span> phoneNumber;</div><div class="line">   &#125;</div></pre></td></tr></table></figure>
</li>
<li><p>还要根据电话号码的值来决定拨打电话按钮的状态</p>
 <figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">handleCallSuspectButton</span><span class="params">(String phoneNumber)</span> </span>&#123;</div><div class="line">       <span class="keyword">if</span> (phoneNumber == <span class="keyword">null</span> || phoneNumber.isEmpty()) &#123;</div><div class="line">           mCallSuspectButton.setText(getString(R.string.crime_call_no_phone_number));</div><div class="line">           mCallSuspectButton.setEnabled(<span class="keyword">false</span>);</div><div class="line">       &#125; <span class="keyword">else</span> &#123;</div><div class="line">           mCallSuspectButton.setText(getString(R.string.crime_call_suspect, phoneNumber));</div><div class="line">           mCallSuspectButton.setEnabled(<span class="keyword">true</span>);</div><div class="line">       &#125;</div><div class="line">   &#125;</div></pre></td></tr></table></figure>
</li>
</ol>
<h3 id="3-2-3-建立拨打电话的隐式-Intent"><a href="#3-2-3-建立拨打电话的隐式-Intent" class="headerlink" title="3.2.3 建立拨打电话的隐式 Intent"></a>3.2.3 建立拨打电话的隐式 Intent</h3><p>这个就和书上的差不多啦~</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line">mCallSuspectButton.setOnClickListener(<span class="keyword">new</span> View.OnClickListener() &#123;</div><div class="line">    <span class="meta">@Override</span></div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onClick</span><span class="params">(View v)</span> </span>&#123;</div><div class="line">        Uri number = Uri.parse(<span class="string">"tel:"</span> + mCrime.getPhoneNumber());</div><div class="line">        Intent i = <span class="keyword">new</span> Intent(Intent.ACTION_DIAL, number);</div><div class="line">        startActivity(i);</div><div class="line">    &#125;</div><div class="line">&#125;);</div></pre></td></tr></table></figure>

          
        
      
    </div>

    <div>
      
    </div>

    <div>
      
    </div>

    <div>
      
    </div>

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal " itemscope itemtype="http://schema.org/Article">
    <link itemprop="mainEntityOfPage" href="https://kniost.github.io/2017-01-09-cj0ajvouy000y1js6vgfqoi6s/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="kniost">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/images/avatar.jpg">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="结网之路">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
            
            
              
                
                <a class="post-title-link" href="/2017-01-09-cj0ajvouy000y1js6vgfqoi6s/" itemprop="url">
                  Android编程权威指南（第二版）学习笔记（十四）—— 第14章 SQLite 数据库
                </a>
              
            
          </h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2017-01-09T10:54:10+08:00">
                2017-01-09
              </time>
            

            

            
          </span>

          
            <span class="post-category" >
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/Android编程权威指南学习笔记/" itemprop="url" rel="index">
                    <span itemprop="name">Android编程权威指南学习笔记</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
              <span class="post-comments-count">
                <span class="post-meta-divider">|</span>
                <span class="post-meta-item-icon">
                  <i class="fa fa-comment-o"></i>
                </span>
                <a href="/2017-01-09-cj0ajvouy000y1js6vgfqoi6s/#comments" itemprop="discussionUrl">
                  <span class="post-comments-count disqus-comment-count" data-disqus-identifier="2017-01-09-cj0ajvouy000y1js6vgfqoi6s/" itemprop="commentCount"></span>
                </a>
              </span>
            
          

          
          

          

          

          

        </div>
      </header>
    


    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <blockquote>
<p>本章主要讲了如何使用 SQLite 进行持久化存储，包含了 CRUD 四个操作，使用基础的 SQLiteOpenHelper 与 Cursor 构造程序</p>
<p>GitHub 地址：<br><a href="https://github.com/kniost/Android-Programming-Big-Nerd-Ranch-Guide-2nd/commit/2927544a20495b24a5c834b447af8b6afc2723e9" target="_blank" rel="external">完成14章所有内容</a></p>
</blockquote>
<hr>
<blockquote>
<p><strong>为什么要用数据库而不是文本文件来存储数据呢？</strong><br>因为如果用普通的文本文件（比如 txt）存储数据，每次读取时都要读取整个文件的内容，完成修改后再全部保存，一旦数据量较大，将十分耗费时间和资源。</p>
</blockquote>
<p>SQLite是类似于MySQL的开源关系型数据库。不同于其他数据库的是,SQLite使用单个文件存储数据,使用SQLite库读取数据。Android标准库包含SQLite库以及配套的一些Java辅助类。</p>
<h1 id="1-定义-Schema（架构）"><a href="#1-定义-Schema（架构）" class="headerlink" title="1. 定义 Schema（架构）"></a>1. 定义 Schema（架构）</h1><p>本应用需要保存的应该是一个 Crime 的全部数据，比如一个表格可以下表这样。</p>
<table>
<thead>
<tr>
<th style="text-align:center">_id</th>
<th style="text-align:center">uuid</th>
<th style="text-align:center">title</th>
<th style="text-align:center">date</th>
<th style="text-align:center">solved</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">1</td>
<td style="text-align:center">13090624138324</td>
<td style="text-align:center">Stolen yougurt</td>
<td style="text-align:center">13090636733242</td>
<td style="text-align:center">0</td>
</tr>
<tr>
<td style="text-align:center">2</td>
<td style="text-align:center">13090274859392</td>
<td style="text-align:center">Dirty sink</td>
<td style="text-align:center">13090732131909</td>
<td style="text-align:center">1</td>
</tr>
</tbody>
</table>
<blockquote>
<p>程序员的一个目标，或者说信条，就是“不要重复造轮子”，也就是说，多花时间思考复用代码的编写和调用，避免在应用中到处使用重复代码。<br>基于上述准则，我们可以使用能统一定义模型层对象的高级 ORM（对象关系映射）工具，不过对于本章代码来说，因为需要掌握更加基础的内容，将会自己实现数据库操作。</p>
</blockquote>
<p>首先创建一个 Package，名为 database， 然后定义数据架构类 CrimeDbSchema.java </p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// CrimeDbSchema.java</span></div><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">CrimeDbSchema</span> </span>&#123;</div><div class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">CrimeTable</span> </span>&#123;</div><div class="line">        <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> String NAME = <span class="string">"crimes"</span>;</div><div class="line"></div><div class="line">        <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">Cols</span> </span>&#123;</div><div class="line">            <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> String UUID = <span class="string">"uuid"</span>;</div><div class="line">            <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> String TITLE = <span class="string">"title"</span>;</div><div class="line">            <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> String DATE = <span class="string">"date"</span>;</div><div class="line">            <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> String SOLVED = <span class="string">"solved"</span>;</div><div class="line">        &#125;</div><div class="line">    &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<blockquote>
<p>这里有一个小 Tip，Android Studio 中有输入一长串前缀的 Live Template，比如要输入<code>public static final String</code>，只需要打 psfs 即可。</p>
</blockquote>
<h1 id="2-初始创建数据库"><a href="#2-初始创建数据库" class="headerlink" title="2. 初始创建数据库"></a>2. 初始创建数据库</h1><p>一般来说，在实际开发中，打开一个数据库之前，由于不知道其是否存在，是否有更新，所以要经过如下步骤：</p>
<ol>
<li>确认目标数据库是否存在。</li>
<li>如果不存在，首先创建数据库，然后创建数据库表以及必需的初始化数据。</li>
<li>如果存在，打开并确认数据库架构是否为最新版本</li>
<li>如果是旧版，就运行相关代码升级到最新版本</li>
</ol>
<p>在 Android 中，提供了一个<code>SQLiteOpenHelper</code>类用于处理这些打开数据库时繁杂的工作。我们可以创建一个<code>SQLiteOpenHelper</code>的子类用于对自己的数据库进行处理，比如：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">CrimeBaseHelper</span> <span class="keyword">extends</span> <span class="title">SQLiteOpenHelper</span> </span>&#123;</div><div class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> VERSION = <span class="number">1</span>;</div><div class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> String DATABASE_NAME = <span class="string">"crimeBase.db"</span>;</div><div class="line"></div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="title">CrimeBaseHelper</span><span class="params">(Context context)</span> </span>&#123;</div><div class="line">        <span class="keyword">super</span>(context, DATABASE_NAME, <span class="keyword">null</span>, VERSION);</div><div class="line">    &#125;</div><div class="line"></div><div class="line">	<span class="comment">// 如果数据库不存在，就调用该函数创建一个数据库</span></div><div class="line">    <span class="meta">@Override</span></div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onCreate</span><span class="params">(SQLiteDatabase db)</span> </span>&#123;</div><div class="line">    	 <span class="comment">// 一定要注意语句之间的空格，因为语句是一个字符串</span></div><div class="line">    	 <span class="comment">// 如果没有空格，对于 SQL 来说，就是无意义的语句</span></div><div class="line">    	 <span class="comment">// 比如下面的 table 后面一定要接一个空格</span></div><div class="line">        db.execSQL(<span class="string">"create table "</span> + CrimeTable.NAME + <span class="string">"("</span> +</div><div class="line">                <span class="string">" _id integer primary key autoincrement, "</span> +</div><div class="line">                CrimeTable.Cols.UUID + <span class="string">", "</span> +</div><div class="line">                CrimeTable.Cols.TITLE + <span class="string">", "</span> +</div><div class="line">                CrimeTable.Cols.DATE + <span class="string">", "</span> +</div><div class="line">                CrimeTable.Cols.SOLVED +</div><div class="line">                <span class="string">")"</span></div><div class="line">        );</div><div class="line">    &#125;</div><div class="line"></div><div class="line">	<span class="comment">// 如果版本升级了，就调用 onUpgrade() 函数</span></div><div class="line">	<span class="meta">@Override</span></div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onUpgrade</span><span class="params">(SQLiteDatabase db, <span class="keyword">int</span> oldVersion, <span class="keyword">int</span> newVersion)</span> </span>&#123;</div><div class="line"></div><div class="line">    &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<blockquote>
<p>由于数据库结构调整太麻烦，在实际开发中最好的做法应该是直接删除数据库文件（最方便的就是卸载程序） :)</p>
</blockquote>
<h1 id="3-打开数据库"><a href="#3-打开数据库" class="headerlink" title="3. 打开数据库"></a>3. 打开数据库</h1><p>然后在 Model 层（在这里是 CrimeLab 单例）中打开数据库</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div></pre></td><td class="code"><pre><div class="line">mContext = context.getApplicationContext();</div><div class="line">mDataBase = <span class="keyword">new</span> CrimeBaseHelper(mContext)</div><div class="line">		.getWritableDatabase();</div></pre></td></tr></table></figure>
<h1 id="4-写入数据库（插入与更新）"><a href="#4-写入数据库（插入与更新）" class="headerlink" title="4. 写入数据库（插入与更新）"></a>4. 写入数据库（插入与更新）</h1><h2 id="4-1-使用-ContentValues"><a href="#4-1-使用-ContentValues" class="headerlink" title="4.1 使用 ContentValues"></a>4.1 使用 ContentValues</h2><p>负责处理数据库写入和更新操作的辅助类是 ContentValues 类。它是个键值存储类，类似于 Java 的 HashMap（查看源码可以发现它就是一个 HashMap）和前面用过的 Bundle。不同的是, ContentValues 专门用于处理SQLite数据。我们需要一个 Model 层的方法，把 Model 层数据转换为ContentValues，由于在 Model 层外，对数据的操作应该只有对 Model 对象的操作，所以这个方法应该是私有的。示例如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">static</span> ContentValues <span class="title">getContentValues</span><span class="params">(Crime crime)</span> </span>&#123;</div><div class="line">    ContentValues values = <span class="keyword">new</span> ContentValues();</div><div class="line">    values.put(Cols.UUID, crime.getId().toString());</div><div class="line">    values.put(Cols.TITLE, crime.getTitle());</div><div class="line">    values.put(Cols.DATE, crime.getDate().getTime());</div><div class="line">    values.put(Cols.SOLVED, crime.isSolved() ? <span class="number">1</span> : <span class="number">0</span>);</div><div class="line">    </div><div class="line">    <span class="keyword">return</span> values;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<h2 id="4-2-插入和更新记录"><a href="#4-2-插入和更新记录" class="headerlink" title="4.2 插入和更新记录"></a>4.2 插入和更新记录</h2><h3 id="4-2-1-插入记录"><a href="#4-2-1-插入记录" class="headerlink" title="4.2.1 插入记录"></a>4.2.1 插入记录</h3><p>准备好了 ContentValues，就可以进行写入数据了，调用<code>SQLiteDatabase.insert(…)</code>方法即可。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">addCrime</span><span class="params">(Crime c)</span> </span>&#123;</div><div class="line">    ContentValues values = getContentValues(c);</div><div class="line">    <span class="comment">/**</span></div><div class="line">     * SQLiteDatabase.insert(String table, String nullColumnHack, ContentValues values)</div><div class="line">     * 第一个参数是表名，第三个参数是键值对</div><div class="line">     * 第二个参数则是当 values 为全空时插入空行</div><div class="line">     * 如果设为 null，则 values 为全空时不插入空行</div><div class="line">     */</div><div class="line">    mDatabase.insert(CrimeTable.NAME, <span class="keyword">null</span>, values);</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<h3 id="4-2-2-更新记录"><a href="#4-2-2-更新记录" class="headerlink" title="4.2.2 更新记录"></a>4.2.2 更新记录</h3><p>更新记录使用的是<code>SQLiteDatabase.update(…)</code>方法。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">updateCrime</span><span class="params">(Crime crime)</span> </span>&#123;</div><div class="line">    String uuidString = crime.getId().toString();</div><div class="line">    ContentValues values = getContentValues(crime);</div><div class="line"></div><div class="line">    mDatabase.update(CrimeTable.NAME, values,</div><div class="line">            Cols.UUID + <span class="string">"=?"</span>,</div><div class="line">            <span class="keyword">new</span> String[] &#123;uuidString&#125;);</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<p>update 方法的原型是：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line">SQLiteDatabase.update(String table, <span class="comment">// 表名</span></div><div class="line">				ContentValues values, <span class="comment">// 键值对</span></div><div class="line">				<span class="comment">// where 后面接的语句，一般是 "columnName = ?"</span></div><div class="line">				String whereClause, </div><div class="line">				<span class="comment">// whereClause 中 ? 代表的语句，可以有多个</span></div><div class="line">				String[] whereArgs</div><div class="line">);</div></pre></td></tr></table></figure>
<p>可以看到，实际上后面两个参数的意思就是 where columnName = columnValue，那么为什么要留出两个参数而不用一个参数解决呢?</p>
<blockquote>
<p>这样做是为了防范 SQL 脚本注入，因为 String 如果本身就带了 SQL 语句，如果不加处理放进数据库执行，就有可能造成灾难性的后果（比如直接 drop 掉所有的表）</p>
</blockquote>
<h1 id="5-读取数据库"><a href="#5-读取数据库" class="headerlink" title="5. 读取数据库"></a>5. 读取数据库</h1><p>读取数据库用到的是 query() 方法，这个方法有许多个重载版本，我们使用下面的版本：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> Cursor <span class="title">query</span><span class="params">(</span></span></div><div class="line">	String table, 			// 表名</div><div class="line">	String[] columns,		// 选中的列名，为 <span class="keyword">null</span> 时选中所有列</div><div class="line">	String where,			// where 语句</div><div class="line">	String[] whereArgs,		// where 语句的参数</div><div class="line">	String groupBy,			// 分组</div><div class="line">	String having,			// 与合计函数一起使用的 having</div><div class="line">	String orderBy,			// 顺序</div><div class="line">	String limit)			<span class="comment">// 限制数量</span></div></pre></td></tr></table></figure>
<p>可以看到返回的是一个 Cursor 对象，下面来探究一下 Cursor 对象</p>
<h2 id="5-1-Cursor-与-CursorWrapper"><a href="#5-1-Cursor-与-CursorWrapper" class="headerlink" title="5.1 Cursor 与 CursorWrapper"></a>5.1 Cursor 与 CursorWrapper</h2><p>Cursor 是个神奇的表数据处理工具，其任务就是封装数据表中的原始字段值。从Cursor获取数据的代码大致如下所示:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">String uuidString = cursor.getString(cursor.getColumnIndex(CrimeTable.Cols.UUID));</div></pre></td></tr></table></figure>
<p>每次要取出一条记录中的一列，都要重复写一次上述代码，所以我们使用 CursorWrapper 建立一个 Cursor 的子类，在其中封装可以转换对象的方法。<br>比如一个类可以这么写：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div></pre></td><td class="code"><pre><div class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">CrimeCursorWrapper</span> <span class="keyword">extends</span> <span class="title">CursorWrapper</span> </span>&#123;</div><div class="line">    <span class="function"><span class="keyword">public</span> <span class="title">CrimeCursorWrapper</span><span class="params">(Cursor cursor)</span> </span>&#123;</div><div class="line">        <span class="keyword">super</span>(cursor);</div><div class="line">    &#125;</div><div class="line"></div><div class="line">    <span class="function"><span class="keyword">public</span> Crime <span class="title">getCrime</span><span class="params">()</span> </span>&#123;</div><div class="line">    	<span class="comment">//这里是从得到的 CursorWrapper 中取出数据</span></div><div class="line">        String uuidString = getString(getColumnIndex(Cols.UUID));</div><div class="line">        String title = getString(getColumnIndex(Cols.TITLE));</div><div class="line">        <span class="keyword">long</span> date = getLong(getColumnIndex(Cols.DATE));</div><div class="line">        <span class="keyword">int</span> isSolved = getInt(getColumnIndex(Cols.SOLVED));</div><div class="line">        </div><div class="line">		<span class="comment">// 然后生成一个 Model 层对象返回，免去了重复写的繁琐</span></div><div class="line">        Crime crime = <span class="keyword">new</span> Crime(UUID.fromString(uuidString));</div><div class="line">        crime.setTitle(title);</div><div class="line">        crime.setDate(<span class="keyword">new</span> Date(date));</div><div class="line">        crime.setSolved(isSolved != <span class="number">0</span>);</div><div class="line"></div><div class="line">        <span class="keyword">return</span> crime;</div><div class="line">    &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<h2 id="5-2-创建-Model-层对象"><a href="#5-2-创建-Model-层对象" class="headerlink" title="5.2 创建 Model 层对象"></a>5.2 创建 Model 层对象</h2><p>首先封装一个数据库查询方法，返回的是自定义的 CrimeCursorWrapper 对象。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">private</span> CrimeCursorWrapper <span class="title">queryCrimes</span><span class="params">(String whereClause, String[] whereArgs)</span> </span>&#123;</div><div class="line">    Cursor cursor = mDatabase.query(</div><div class="line">            CrimeTable.NAME,</div><div class="line">            <span class="keyword">null</span>, <span class="comment">// Columns -- use null to select all columns</span></div><div class="line">            whereClause,</div><div class="line">            whereArgs,</div><div class="line">            <span class="keyword">null</span>,</div><div class="line">            <span class="keyword">null</span>,</div><div class="line">            <span class="keyword">null</span></div><div class="line">    );</div><div class="line">    <span class="keyword">return</span> <span class="keyword">new</span> CrimeCursorWrapper(cursor);</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<p>再从 CursorWrapper 中获取数据</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// 获取所有数据</span></div><div class="line"><span class="function"><span class="keyword">public</span> List&lt;Crime&gt; <span class="title">getCrimes</span><span class="params">()</span> </span>&#123;</div><div class="line">    List&lt;Crime&gt; crimes = <span class="keyword">new</span> ArrayList&lt;&gt;();</div><div class="line"></div><div class="line">    CrimeCursorWrapper cursor = queryCrimes(<span class="keyword">null</span>, <span class="keyword">null</span>);</div><div class="line">    <span class="keyword">try</span> &#123;</div><div class="line">        cursor.moveToFirst();</div><div class="line">        <span class="keyword">while</span> (!cursor.isAfterLast()) &#123;</div><div class="line">            crimes.add(cursor.getCrime());</div><div class="line">            cursor.moveToNext();</div><div class="line">        &#125;</div><div class="line">    &#125; <span class="keyword">finally</span> &#123;</div><div class="line">        cursor.close();</div><div class="line">    &#125;</div><div class="line"></div><div class="line">    <span class="keyword">return</span> crimes;</div><div class="line">&#125;</div><div class="line"><span class="comment">//获取单个记录</span></div><div class="line"><span class="function"><span class="keyword">public</span> Crime <span class="title">getCrime</span><span class="params">(UUID id)</span> </span>&#123;</div><div class="line">    CrimeCursorWrapper cursor = queryCrimes(</div><div class="line">            Cols.UUID + <span class="string">" = ?"</span>,</div><div class="line">            <span class="keyword">new</span> String[] &#123; id.toString() &#125;</div><div class="line">    );</div><div class="line"></div><div class="line">    <span class="keyword">try</span> &#123;</div><div class="line">        <span class="keyword">if</span> (cursor.getCount() == <span class="number">0</span>) &#123;</div><div class="line">            <span class="keyword">return</span> <span class="keyword">null</span>;</div><div class="line">        &#125;</div><div class="line"></div><div class="line">        cursor.moveToFirst();</div><div class="line">        <span class="keyword">return</span> cursor.getCrime();</div><div class="line">    &#125; <span class="keyword">finally</span> &#123;</div><div class="line">        cursor.close();</div><div class="line">    &#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<h1 id="6-Application-Context-应用上下文"><a href="#6-Application-Context-应用上下文" class="headerlink" title="6 Application Context(应用上下文)"></a>6 Application Context(应用上下文)</h1><p>前面,我们在CrimeLab的构造方法中使用了Application Context。</p>
<pre><code class="java"><span class="function"><span class="keyword">private</span> <span class="title">CrimeLab</span><span class="params">(Context context)</span> </span>{
    mContext = context.getApplicationContext();
    ...
}
&lt;!--￼<span class="number">12</span>--&gt;

```java
<span class="comment">// CrimeFragment.java</span>
<span class="meta">@Override</span>
<span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">onOptionsItemSelected</span><span class="params">(MenuItem item)</span> </span>{
    <span class="keyword">switch</span> (item.getItemId()) {
        <span class="keyword">case</span> R.id.menu_item_delete_crime:
            CrimeLab.get(getActivity()).deleteCrime(mCrime);
            getActivity().finish();
            <span class="keyword">return</span> <span class="keyword">true</span>;
        <span class="keyword">default</span>:
            <span class="keyword">return</span> <span class="keyword">super</span>.onOptionsItemSelected(item);
    }
}
</code></pre>

          
        
      
    </div>

    <div>
      
    </div>

    <div>
      
    </div>

    <div>
      
    </div>

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal " itemscope itemtype="http://schema.org/Article">
    <link itemprop="mainEntityOfPage" href="https://kniost.github.io/2016-12-24-cj0ajvoue000a1js6rvhaxbq6/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="kniost">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/images/avatar.jpg">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="结网之路">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
            
            
              
                
                <a class="post-title-link" href="/2016-12-24-cj0ajvoue000a1js6rvhaxbq6/" itemprop="url">
                  Android 常用高级调试技巧
                </a>
              
            
          </h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2016-12-24T15:52:00+08:00">
                2016-12-24
              </time>
            

            

            
          </span>

          
            <span class="post-category" >
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/Android开发技巧/" itemprop="url" rel="index">
                    <span itemprop="name">Android开发技巧</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
              <span class="post-comments-count">
                <span class="post-meta-divider">|</span>
                <span class="post-meta-item-icon">
                  <i class="fa fa-comment-o"></i>
                </span>
                <a href="/2016-12-24-cj0ajvoue000a1js6rvhaxbq6/#comments" itemprop="discussionUrl">
                  <span class="post-comments-count disqus-comment-count" data-disqus-identifier="2016-12-24-cj0ajvoue000a1js6rvhaxbq6/" itemprop="commentCount"></span>
                </a>
              </span>
            
          

          
          

          

          

          

        </div>
      </header>
    


    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <blockquote>
<p>在刚开始开发 Android 的时候，很多人只会使用 Log 调试以及简单的断点调试。但是为了更快速地调试，还是需要掌握一些高级一点的调试技巧，以提升自己的效率，下面是我找到和使用的一些技巧</p>
</blockquote>
<h1 id="1-条件断点"><a href="#1-条件断点" class="headerlink" title="1. 条件断点"></a>1. 条件断点</h1><p>条件断点(Conditional Breakpoints)指的是调试中为断点设置条件，当满足设置条件时程序就会停下。条件断点在循环中极为好用，比如有100次的循环，而你就想看第53次的程序运行过程，总不能一次一次运行看，这个时候就需要条件断点了。</p>
<h2 id="用法："><a href="#用法：" class="headerlink" title="用法："></a>用法：</h2><p>在设置的断点上点击右键，就会弹出断点的简单设置对话框，在 <code>Condition</code> 框里面填写上你所需要中断的条件即可，此后调试程序时，程序就会在满足设置的条件时才会停下。</p>
<h2 id="示例："><a href="#示例：" class="headerlink" title="示例："></a>示例：</h2><p>首先设置断点，在断点上右键，填写条件</p>
<img src="/2016-12-24-cj0ajvoue000a1js6rvhaxbq6/conditionalBreakPoint.png" alt="条件断点" title="条件断点">
<p>执行 debug，会自动在满足条件处中断</p>
<img src="/2016-12-24-cj0ajvoue000a1js6rvhaxbq6/conditionalBreakPoint-2.png" alt="条件断点" title="条件断点">
<h1 id="2-日志断点"><a href="#2-日志断点" class="headerlink" title="2. 日志断点"></a>2. 日志断点</h1><p>顾名思义，日志断点(Logging Breakpoints)就是在断点处输出日志，从而避免了在代码中写 Log，然后再重新运行程序。</p>
<h2 id="用法：-1"><a href="#用法：-1" class="headerlink" title="用法："></a>用法：</h2><p>在设置的断点上右键，将 Suspend 置为非选中状态，然后会出现一些详细设置，在下面的 <code>Log evaluated Expression</code> 填写上你需要输出的信息。其实相当于调试程序在这里帮你打出了 Log，但是根据实际效果推测，由于还是一个断点，系统是先暂停，再打印 Log，最后再恢复，所以速度上会更慢一些。</p>
<h2 id="示例：-1"><a href="#示例：-1" class="headerlink" title="示例："></a>示例：</h2><p>首先设置断点，在断点上右键，反选 Suspend，填上需要 log 的信息。<br><img src="/2016-12-24-cj0ajvoue000a1js6rvhaxbq6/log_without_Log.png" alt="日志断点" title="日志断点"></p>
<p>执行 Debug 以后，会出现结果：</p>
<img src="/2016-12-24-cj0ajvoue000a1js6rvhaxbq6/logResult.png" alt="日志断点结果" title="日志断点结果">
<blockquote>
<p>可以看到，sum 的值还没有发生变化时就已经 log 了，也就是说，在执行断点这行程序之前就已经进入断点了。在这个例子中，如果想看 sum 变化之后的值，就需要在 sum += i; 这行代码之后加断点。</p>
</blockquote>
<h1 id="3-临时断点"><a href="#3-临时断点" class="headerlink" title="3. 临时断点"></a>3. 临时断点</h1><p>临时断点(Temporary Breakpoints)只会在第一次运行到这个断点的时候停下，然后就会被自动移除了，在对不需要重复调试的地方比较有效。（其实我觉得可以叫一次断点）</p>
<h2 id="用法：-2"><a href="#用法：-2" class="headerlink" title="用法："></a>用法：</h2><p>Windows/ Linux : 按住 Alt 点击代码左侧断点位置<br>macOS : 按住 Option 点击代码左侧断点位置</p>
<blockquote>
<p><strong>如果在有断点的情况下再使用上述方法点击断点，将会使断点在有效和失效状态下切换。</strong></p>
</blockquote>
<h1 id="4-附着进程调试"><a href="#4-附着进程调试" class="headerlink" title="4. 附着进程调试"></a>4. 附着进程调试</h1><p>附着进程调试(Attach debugger to Android process)，在了解这个选项之前，总是喜欢点击调试，让 Gradle 重新编译，虽然有 Instant Run 的速度加持，但依然比较缓慢。<br>在这个模式下，你不需要重新编译，只要这个 App 是打开的，就可以直接点击该按钮，就可以快速进入调试，对于测试来说非常方便。</p>
<h2 id="用法：-3"><a href="#用法：-3" class="headerlink" title="用法："></a>用法：</h2><ol>
<li><p>点击菜单栏中的 Run -&gt;Attach Debugger to Android Process ，然后选择 App 所在的进程</p>
</li>
<li><p>或者点击下列按钮</p>
</li>
</ol>
<img src="/2016-12-24-cj0ajvoue000a1js6rvhaxbq6/attachDebuggerToProcess.png" alt="附着进程调试" title="附着进程调试">
<h1 id="5-观察变量"><a href="#5-观察变量" class="headerlink" title="5. 观察变量"></a>5. 观察变量</h1><p>在断点处停下时，将鼠标悬停在变量上或者按住 Option(Alt) 键点击变量时即可显示该变量的值</p>
<h1 id="6-计算表达式"><a href="#6-计算表达式" class="headerlink" title="6. 计算表达式"></a>6. 计算表达式</h1><p><strong>计算表达式</strong>(Evaluate Expression)，用来在调试过程中查看变量的值，或者计算任何复杂的自定义表达式。</p>
<p>但它最好的一点不仅是可以查看变量的值且进行运算，还能<strong>执行赋值语句</strong>！也就是说，它能在运行中实时修改变量，方便测试各种功能。</p>
<h2 id="用法：-4"><a href="#用法：-4" class="headerlink" title="用法："></a>用法：</h2><p>在断点中断后，按 Option(Alt) + F8，或者在 Run 菜单中找到 Evaluate Expression，然后输入想做的操作</p>
<h2 id="示例：-2"><a href="#示例：-2" class="headerlink" title="示例："></a>示例：</h2><p>首先在断点处停下，我设置的条件是 i == 6，此时 sum 的值为15</p>
<img src="/2016-12-24-cj0ajvoue000a1js6rvhaxbq6/evaluateExpression1.png" alt="计算表达式" title="计算表达式">
<p>进入 Evaluate Expression，直接输入 sum 回车，能看到 sum 的值，也可以直接对 sum 进行修改，比如 sum +=20，结果是35</p>
<img src="/2016-12-24-cj0ajvoue000a1js6rvhaxbq6/evaluateExpression2.png" alt="计算表达式2" title="计算表达式2">
<p>回到程序界面，发现 sum 的值已经改变了</p>
<img src="/2016-12-24-cj0ajvoue000a1js6rvhaxbq6/evaluateExpression3.png" alt="计算表达式3" title="计算表达式3">
<p>这非常适合对于在后台服务器获取数据时调试一些需要后台修改的数据，省时省力</p>

          
        
      
    </div>

    <div>
      
    </div>

    <div>
      
    </div>

    <div>
      
    </div>

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal " itemscope itemtype="http://schema.org/Article">
    <link itemprop="mainEntityOfPage" href="https://kniost.github.io/2016-11-24-cj0ajvout000n1js69l6ox3jb/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="kniost">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/images/avatar.jpg">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="结网之路">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
            
            
              
                
                <a class="post-title-link" href="/2016-11-24-cj0ajvout000n1js69l6ox3jb/" itemprop="url">
                  Android编程权威指南（第二版）学习笔记（十三）—— 第13章 工具栏（Toolbar）
                </a>
              
            
          </h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2016-11-24T11:45:10+08:00">
                2016-11-24
              </time>
            

            

            
          </span>

          
            <span class="post-category" >
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/Android编程权威指南学习笔记/" itemprop="url" rel="index">
                    <span itemprop="name">Android编程权威指南学习笔记</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
              <span class="post-comments-count">
                <span class="post-meta-divider">|</span>
                <span class="post-meta-item-icon">
                  <i class="fa fa-comment-o"></i>
                </span>
                <a href="/2016-11-24-cj0ajvout000n1js69l6ox3jb/#comments" itemprop="discussionUrl">
                  <span class="post-comments-count disqus-comment-count" data-disqus-identifier="2016-11-24-cj0ajvout000n1js69l6ox3jb/" itemprop="commentCount"></span>
                </a>
              </span>
            
          

          
          

          

          

          

        </div>
      </header>
    


    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <blockquote>
<p>本章主要讲了如何使用 ToolBar，顺便普及了 AppCompat 的由来和 app 命名空间的知识</p>
<p>GitHub 地址：<br><a href="https://github.com/kniost/Android-Programming-Big-Nerd-Ranch-Guide-2nd/commit/62060a5d9c3ba3edda36db99befda973d175ede9" target="_blank" rel="external">完成13章,未完成挑战</a><br><a href="https://github.com/kniost/Android-Programming-Big-Nerd-Ranch-Guide-2nd/commit/7c0e3e71cac0d8c400d1cc4fbbd5ef9a79e0055a" target="_blank" rel="external">完成13章挑战1</a><br><a href="https://github.com/kniost/Android-Programming-Big-Nerd-Ranch-Guide-2nd/commit/ea6a018b767644ce400eeee44afda18b9f9fca94" target="_blank" rel="external">完成13章挑战2，3</a></p>
</blockquote>
<h1 id="1-ToolBar-与-ActionBar"><a href="#1-ToolBar-与-ActionBar" class="headerlink" title="1. ToolBar 与 ActionBar"></a>1. ToolBar 与 ActionBar</h1><p>首先，两者给我们最直观的印象就是 ToolBar 界面更美观。ToolBar 左边不再放置图标，右边菜单项的间距也更小。另外就是向上的导航按钮， ActionBar 上的这个按钮不够醒目，只是旁边按钮的附属物。除了感官上的差异，在使用上，ToolBar 比 ActionBar 更灵活。 ActionBar 的使用限制多多，比如，整个应用只能配置一个 ActionBar 且位置及尺寸必须固定(在屏幕顶部)，而 ToolBar 就没有这些限制。</p>
<p>本章使用的 ToolBar 应用了 AppCompat 主题。如有需要，也可以通过 activity 和fragment布局定制标准视图的 ToolBar 。<strong>我们可以在屏幕的任何位置摆放 ToolBar ，甚至可以在同一屏配置多个 ToolBar</strong>。 应用设计的自由度由此大大提高了，例如，可以为每个fragment定制专用 ToolBar 。可以想象，在同一个用户界面托管多个fragment时，每个fragment都由自己的 ToolBar 控制，这比所有 fragment 共享一个位于屏幕顶部的 ToolBar 方便多了。</p>
<p>此外， ToolBar 还能支持内嵌视图和调整高度，这极大丰富了应用的交互使用模式。毫不夸张 地说，应用设计最大的局限就是我们的想象空间。</p>
<h1 id="2-引入-AppCompat"><a href="#2-引入-AppCompat" class="headerlink" title="2. 引入 AppCompat"></a>2. 引入 AppCompat</h1><h2 id="2-1-使用-AppCompat-库"><a href="#2-1-使用-AppCompat-库" class="headerlink" title="2.1 使用 AppCompat 库"></a>2.1 使用 AppCompat 库</h2><p>完全整合 AppCompat 库，我们需要:</p>
<ol>
<li>添加AppCompat依赖项;（在前面的章节已经做过）</li>
<li>使用一种AppCompat主题;</li>
<li>确保所有的activitiy都是AppCompatActivity子类。</li>
</ol>
<h3 id="2-1-1-更新主题"><a href="#2-1-1-更新主题" class="headerlink" title="2.1.1 更新主题"></a>2.1.1 更新主题</h3><p>添加了依赖库，接下来至少要使用一种 AppCompat 主题。</p>
<p>AppCompat库自带以下三种主题：</p>
<ul>
<li>Theme.AppCompat:黑色主题</li>
<li>Theme.AppCompat.Light:浅色主题</li>
<li>Theme.AppCompat.Light.DarkActionBar:带黑色工具栏的浅色主题</li>
</ul>
<p>应用级别的主题设置在<code>AndroidManifest.xml</code>文件中。主题也可按 activity 配置。打开 <code>AndroidManifest.xml</code> 文件，更改 application 标签的<code>android:theme</code>属性，</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="tag">&lt;<span class="name">resources</span>&gt;</span>    <span class="tag">&lt;<span class="name">style</span> <span class="attr">name</span>=<span class="string">"AppTheme"</span> <span class="attr">parent</span>=<span class="string">"Theme.AppCompat.Light.DarkActionBar"</span>&gt;</span><span class="undefined">    </span><span class="tag">&lt;/<span class="name">style</span>&gt;</span><span class="tag">&lt;/<span class="name">resources</span>&gt;</span></div></pre></td></tr></table></figure>
<h3 id="2-1-2-使用-AppCompatActivity"><a href="#2-1-2-使用-AppCompatActivity" class="headerlink" title="2.1.2 使用 AppCompatActivity"></a>2.1.2 使用 AppCompatActivity</h3><p>将所有继承 FragmentActivity 的 activity 更换成继承 AppCompatActivity。</p>
<h1 id="3-ToolBar-菜单"><a href="#3-ToolBar-菜单" class="headerlink" title="3. ToolBar 菜单"></a>3. ToolBar 菜单</h1><p>工具栏菜单由操作项(又称为菜单项)组成，它占据着工具栏的右上方区域。操作项的操作应用于当前屏幕，甚至整个应用。</p>
<h2 id="3-1-在-XML-文件中定义菜单"><a href="#3-1-在-XML-文件中定义菜单" class="headerlink" title="3.1 在 XML 文件中定义菜单"></a>3.1 在 XML 文件中定义菜单</h2><p>菜单是一种类似于布局的资源。创建菜单定义文件并放置在res/menu目录下，Android会自动 生成相应的资源ID。随后，在代码中实例化菜单时，就可以直接使用。</p>
<h4 id="例子：fragment-crime-list-xml"><a href="#例子：fragment-crime-list-xml" class="headerlink" title="例子：fragment_crime_list.xml"></a>例子：fragment_crime_list.xml</h4><figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line">&lt;?xml version="1.0" encoding="utf-8"?&gt;    <span class="tag">&lt;<span class="name">menu</span> <span class="attr">xmlns:android</span>=<span class="string">"http://schemas.android.com/apk/res/android"</span>          <span class="attr">xmlns:app</span>=<span class="string">"http://schemas.android.com/apk/res-auto"</span>&gt;</span>        <span class="tag">&lt;<span class="name">item</span>            <span class="attr">android:id</span>=<span class="string">"@+id/menu_item_new_crime"</span>            <span class="attr">android:icon</span>=<span class="string">"@android:drawable/ic_menu_add"</span>            <span class="attr">android:title</span>=<span class="string">"@string/new_crime"</span>            <span class="attr">app:showAsAction</span>=<span class="string">"ifRoom|withText"</span>/&gt;</span><span class="tag">&lt;/<span class="name">menu</span>&gt;</span></div></pre></td></tr></table></figure>
<p>你可能注意到了上面的 app:showAsAction 属性，下面来分析一下：</p>
<h3 id="3-1-1-showAsAction-属性的作用"><a href="#3-1-1-showAsAction-属性的作用" class="headerlink" title="3.1.1 showAsAction 属性的作用"></a>3.1.1 showAsAction 属性的作用</h3><p>showAsAction属性用于指定菜单选项是显示在工具栏上，还是隐藏于溢出菜单(overflow menu)。该属性当前设置为 ifRoom 和 withText 的组合值。因此，只要空间足够，菜单项图标及其文字描述都会显示在工具栏上。如空间仅够显示菜单项图标，文字描述就不会显示。如空间大小不够显示任何项，菜单项就会隐藏到溢出菜单中。</p>
<h3 id="3-1-2-应用命名空间"><a href="#3-1-2-应用命名空间" class="headerlink" title="3.1.2 应用命名空间"></a>3.1.2 应用命名空间</h3><p>注意，不同于常见的android命名空间声明，fragment_crime_list.xml文件使用xmlns标签定义了全新的app命名空间。指定showAsAction属性时，就用了这个新定义的命名空间。<br><strong>基于历史代码的原因，AppCompat库需要使用app命名空间</strong>。操作栏API随Android 3.0引入。 为支持各种旧系统版本设备，早期创建的AppCompat库捆绑了兼容版操作栏。在运行Android 2.3 或更早版本系统的设备上，菜单及其相应的XML文件是确实存在的，但是android:showAsAction属性是随着操作栏的发布才添加的。<br>AppCompat库不希望使用原生showAsAction属性，因此，它提供了定制版showAsAction属性(app:showAsAction)。</p>
<h2 id="3-2-创建菜单"><a href="#3-2-创建菜单" class="headerlink" title="3.2 创建菜单"></a>3.2 创建菜单</h2><p>在代码中,Activity类提供了管理菜单的回调函数。需要选项菜单时,Android会调用 Activity 的onCreateOptionsMenu(Menu)方法。</p>
<p>Fragment 也有一套自己的选项菜单回调函数。以下为创建菜单和响应菜单项选择事件的两个回调方法:</p>
<p>```java<br>public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)<br>public boolean onOptionsItemSelected(MenuItem item)<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line">### 3.2.1 实例化菜单在CrimeListFragment.java中,覆盖onCreateOptionsMenu(Menu, MenuInflater)方法,实例化fragment_crime\_list.xml中定义的菜单，如下：</div><div class="line"></div><div class="line">```java</div><div class="line">@Overridepublic void onCreateOptionsMenu(Menu menu, MenuInflater inflater) &#123;    super.onCreateOptionsMenu(menu, inflater);    inflater.inflate(R.menu.fragment_crime_list, menu);&#125;</div></pre></td></tr></table></figure></p>
<h3 id="3-2-2-使-Fragment-初始化菜单"><a href="#3-2-2-使-Fragment-初始化菜单" class="headerlink" title="3.2.2 使 Fragment 初始化菜单"></a>3.2.2 使 Fragment 初始化菜单</h3><p>要通知FragmentManager,需调用以下方法:<br><code>public void setHasOptionsMenu(boolean hasMenu)</code></p>
<p>在CrimeListFragment.onCreate(…)方法中,让FragmentManager知道CrimeListFragment<br>需接收选项菜单方法回调。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="meta">@Override</span><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onCreate</span><span class="params">(Bundle savedInstanceState)</span> </span>&#123;    <span class="keyword">super</span>.onCreate(savedInstanceState);    setHasOptionsMenu(<span class="keyword">true</span>);&#125;</div></pre></td></tr></table></figure>
<h3 id="3-2-3-让菜单发挥作用"><a href="#3-2-3-让菜单发挥作用" class="headerlink" title="3.2.3 让菜单发挥作用"></a>3.2.3 让菜单发挥作用</h3><p>在初始化菜单后，我们已经能在应用界面上看到菜单了，现在需要让菜单能够有实际的作用，比如新增一个 Crime，要达到这个目的，我们需要做下面几件事：</p>
<ol>
<li>在 Model 层增加一个新增的函数</li>
<li><p>在 Controller 层增加菜单监听</p>
 <figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div></pre></td><td class="code"><pre><div class="line"><span class="meta">@Override</span>  <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">onOptionsItemSelected</span><span class="params">(MenuItem item)</span> </span>&#123;   <span class="keyword">switch</span> (item.getItemId()) &#123;</div><div class="line">   <span class="keyword">case</span> R.id.menu_item_new_crime:</div><div class="line">   		Crime crime = <span class="keyword">new</span> Crime();         CrimeLab.get(getActivity()).addCrime(crime);         Intent intent = CrimePagerActivity                 .newIntent(getActivity(), crime.getId());         startActivity(intent);         <span class="keyword">return</span> <span class="keyword">true</span>;</div><div class="line">      <span class="keyword">default</span>:</div><div class="line">		<span class="keyword">super</span>.onOptionsItemSelected(item);</div><div class="line">     &#125;</div><div class="line">  &#125;</div></pre></td></tr></table></figure>
</li>
</ol>
<h2 id="3-3-实现层级式导航"><a href="#3-3-实现层级式导航" class="headerlink" title="3.3 实现层级式导航"></a>3.3 实现层级式导航</h2><p>目前为止,CriminalIntent 应用主要靠后退键在应用内导航。后退键导航又称为临时性导航, 只能返回到上一次浏览过的用户界面;而层级式导航(hierarchical navigation,有时又称为ancestral navigation)可在应用内逐级向上导航。</p>
<p>有了层级式导航,用户可点击工作栏左边的向上按钮向上导航。在Jelly Bean(API 16级)设备 上,可轻松实现层级式导航。但在这之前,开发者只能自己动手处理向上按钮的显示和点击事件。<br>打开 AndroidManifest.xml , 添加 parentActivityName 属性 , 开启 CriminalIntent应用的层级式导航。</p>
<p>```xml</p>
<p><activity android:name=".CrimePagerActivity" android:label="@string/app_name" android:parentactivityname=".CrimeListActivity"><br></activity><br><figure class="highlight plain"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div><div class="line">28</div><div class="line">29</div><div class="line">30</div><div class="line">31</div><div class="line">32</div><div class="line">33</div><div class="line">34</div><div class="line">35</div><div class="line">36</div><div class="line">37</div><div class="line">38</div><div class="line">39</div><div class="line">40</div><div class="line">41</div></pre></td><td class="code"><pre><div class="line">### 层级导航的工作原理在CrimePagerActivity 界面,无论按哪个按钮导航,都是回到CrimeListActivity界面。虽然结果一样,但它们各自的后台实现机制却大不相同。</div><div class="line"></div><div class="line">知道这一点很重要,因为取决于具体应用,向上导航很可能会让用户迷失在众多activity中(这里指回退栈内的众多activity)。用户点击向上按钮自CrimePagerActivity界面向上导航时,如下的intent会被创建:</div><div class="line"></div><div class="line">```javaIntent intent = new Intent(this, CrimeListActivity.class);intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);startActivity(intent);finish();</div><div class="line">````FLAG_ACTIVITY_CLEAR_TOP`指示Android在回退栈中寻找指定的activity实例。如存在,则弹出栈内所有其他activity,让启动的目标activity出现在栈顶(显示在屏幕上)。</div><div class="line"></div><div class="line"># 4. 挑战练习</div><div class="line">## 4.1 删除 crime 记录</div><div class="line">与增加 Crime 记录差不多，首先在 Model 中增加删除方法，然后在 CrimeFragment 中增加菜单（在这之前先建立一个菜单）</div><div class="line"></div><div class="line">1. 在 onCreate 中添加 `setHasOptionsMenu(true);`</div><div class="line">2. 重写 onCreateOptionsMenu() 方法</div><div class="line">3. 重写 onOptionsItemSelected() 方法</div><div class="line"></div><div class="line">## 4.2 优化字符串资源显示</div><div class="line">书上已经写了方法，我就不赘述了</div><div class="line"></div><div class="line">## 4.3 用于 RecyclerView 的空视图</div><div class="line">在 CrimeListFragment 的根布局中加入和列表同级的一个 TextView，此时其中`fragment_crime_list.xml` 中代码如下：</div><div class="line"></div><div class="line">```xml</div><div class="line">&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;</div><div class="line">&lt;RelativeLayout</div><div class="line">    xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;</div><div class="line">    android:layout_width=&quot;match_parent&quot;</div><div class="line">    android:layout_height=&quot;match_parent&quot;&gt;</div><div class="line"></div><div class="line">    &lt;android.support.v7.widget.RecyclerView</div><div class="line">        android:id=&quot;@+id/crime_recycler_view&quot;</div><div class="line">        android:layout_width=&quot;match_parent&quot;</div><div class="line">        android:layout_height=&quot;match_parent&quot;/&gt;</div><div class="line"></div><div class="line">    &lt;TextView</div><div class="line">        android:id=&quot;@+id/crime_set_empty_text_view&quot;</div><div class="line">        android:layout_width=&quot;match_parent&quot;</div><div class="line">        android:layout_height=&quot;match_parent&quot;</div><div class="line">        android:gravity=&quot;center&quot;</div><div class="line">        android:textSize=&quot;20sp&quot;</div><div class="line">        android:text=&quot;@string/empty_crime_set&quot;/&gt;</div><div class="line">&lt;/RelativeLayout&gt;</div></pre></td></tr></table></figure></p>
<p>然后在 CrimeListFragment 中引用该 TextView，并设置和添加 Crime 菜单选项相同的 ClickListener 的内容。</p>
<p>最后，在 updateUI() 方法中，获取到数据集时，判断一下数据集的长度，如果大于零， 隐藏这个 TextView，然后执行之前一系列显示的操作；如果为零，就隐藏 RecyclerView，显示该 TextView。</p>

          
        
      
    </div>

    <div>
      
    </div>

    <div>
      
    </div>

    <div>
      
    </div>

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal " itemscope itemtype="http://schema.org/Article">
    <link itemprop="mainEntityOfPage" href="https://kniost.github.io/2016-11-23-cj0ajvour000k1js6die3v2kv/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="kniost">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/images/avatar.jpg">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="结网之路">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
            
            
              
                
                <a class="post-title-link" href="/2016-11-23-cj0ajvour000k1js6die3v2kv/" itemprop="url">
                  Android编程权威指南（第二版）学习笔记（十二）—— 第12章 对话框
                </a>
              
            
          </h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2016-11-23T16:04:10+08:00">
                2016-11-23
              </time>
            

            

            
          </span>

          
            <span class="post-category" >
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/Android编程权威指南学习笔记/" itemprop="url" rel="index">
                    <span itemprop="name">Android编程权威指南学习笔记</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
              <span class="post-comments-count">
                <span class="post-meta-divider">|</span>
                <span class="post-meta-item-icon">
                  <i class="fa fa-comment-o"></i>
                </span>
                <a href="/2016-11-23-cj0ajvour000k1js6die3v2kv/#comments" itemprop="discussionUrl">
                  <span class="post-comments-count disqus-comment-count" data-disqus-identifier="2016-11-23-cj0ajvour000k1js6die3v2kv/" itemprop="commentCount"></span>
                </a>
              </span>
            
          

          
          

          

          

          

        </div>
      </header>
    


    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <blockquote>
<p>本章主要介绍了如何使用内嵌在 fragment 中的对话框，以及 fragment 之间如何传递数据</p>
<p>GitHub 地址：<br><a href="https://github.com/kniost/Android-Programming-Big-Nerd-Ranch-Guide-2nd/commit/20c265c6dd9e122fc169eed8dc90da60f275f360" target="_blank" rel="external">完成第12章</a></p>
</blockquote>
<h1 id="1-使用-DialogFragment"><a href="#1-使用-DialogFragment" class="headerlink" title="1. 使用 DialogFragment"></a>1. 使用 DialogFragment</h1><h2 id="1-1-AppCompat-兼容库"><a href="#1-1-AppCompat-兼容库" class="headerlink" title="1.1 AppCompat 兼容库"></a>1.1 AppCompat 兼容库</h2><blockquote>
<p>Google 推出 AppCompat 兼容库是为了让所有Android用户都能体验到新特性。AppCompat兼容库能通过支持库的方式将部分最新系统的特色功能移植到Android旧版本系统中。</p>
</blockquote>
<p>应该在 Porject Structure 中添加 appcompat-v7 的依赖。</p>
<h2 id="1-2-创建-DialogFragment"><a href="#1-2-创建-DialogFragment" class="headerlink" title="1.2 创建 DialogFragment"></a>1.2 创建 DialogFragment</h2><blockquote>
<p>建议将 <code>AlertDialog</code> 封装在 <code>DialogFragment</code> (Fragment的子类)实例中使用。当然，不使用<code>DialogFragment</code> 也可显示 <code>AlertDialog</code> 视图,但不推荐这样做。使用 <code>FragmentManager</code> 管理对话框,可以更灵活地显示对话框。</p>
<p>如果旋转设备，单独使用的 <code>AlertDialog</code> 会消失，而封装在 fragment 中的 <code>AlertDialog</code> 则不会有此问题(旋转后,对话框会被重建恢复)。</p>
</blockquote>
<ul>
<li>首先需要一个针对 dialog 的布局文件，如 DatePicker 作为根元素的 Dialog 用于选择日期。</li>
</ul>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div></pre></td><td class="code"><pre><div class="line">&lt;?xml version="1.0" encoding="utf-8"?&gt;</div><div class="line"><span class="tag">&lt;<span class="name">DatePicker</span> <span class="attr">xmlns:android</span>=<span class="string">"http://schemas.android.com/apk/res/android"</span></span></div><div class="line">	<span class="attr">android:id</span>=<span class="string">"@+id/dialog_date_date_picker"</span></div><div class="line">	<span class="attr">android:layout_width</span>=<span class="string">"wrap_content"</span></div><div class="line">	<span class="attr">android:layout_height</span>=<span class="string">"wrap_content"</span></div><div class="line">	<span class="attr">android:calendarViewShown</span>=<span class="string">"false"</span>&gt;</div><div class="line">	<span class="comment">&lt;!--旧版系统会使用 calenderViewShown 属性--&gt;</span></div><div class="line"></div><div class="line"><span class="tag">&lt;/<span class="name">DatePicker</span>&gt;</span></div></pre></td></tr></table></figure>
<ul>
<li>然后新建一个父类是 <code>DialogFragment</code> 的类 <code>DatePickerFragment</code>，重写其中的 <code>onCreateDialog</code> 方法，返回一个<code>AlertDialog</code></li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div></pre></td><td class="code"><pre><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">public</span> Dialog <span class="title">onCreateDialog</span><span class="params">(Bundle savedInstanceState)</span> </span>&#123;</div><div class="line">	<span class="comment">// 使用 LayoutInflater 引用布局文件创建用于显示 Dialog 的 View</span></div><div class="line">	View v = LayoutInflater.from(getActivity())</div><div class="line">                .inflate(R.layout.dialog_date, <span class="keyword">null</span>);</div><div class="line">                </div><div class="line">	<span class="keyword">return</span> <span class="keyword">new</span> AlertDialog.Builder(getActivity())</div><div class="line">		.setView(v) <span class="comment">// 设置视图</span></div><div class="line">		.setTitle(R.string.date_picker_title) <span class="comment">//设置标题</span></div><div class="line">		<span class="comment">// 设置 OK 按钮，OnClickListener 暂时留空</span></div><div class="line">		.setPositiveButton(android.R.string.ok, <span class="keyword">null</span>)</div><div class="line">		<span class="comment">// 使用 Builder 的 create() 方法创建 Dialog 并返回</span></div><div class="line">		.create(); </div><div class="line">&#125;</div></pre></td></tr></table></figure>
<p>在使用 DialogFragment 时，使用成员方法 show 来显示 dialog：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// 在 Fragment 中为 DatePickerFragment 添加一个 Tag</span></div><div class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String DIALOG_DATE = <span class="string">"DialogDate"</span>;</div><div class="line">……</div><div class="line"><span class="comment">// 在 Fragment 内部获取 FragmentManager</span></div><div class="line">FragmentManager manager = getFragmentManager(); </div><div class="line">DatePickerFragment dialog = <span class="keyword">new</span> DatePickerFragment();</div><div class="line"><span class="comment">// 显示对话框</span></div><div class="line">dialog.show(manager, DIALOG_DATE);</div></pre></td></tr></table></figure>
<h1 id="2-fragment-之间的数据传递"><a href="#2-fragment-之间的数据传递" class="headerlink" title="2. fragment 之间的数据传递"></a>2. fragment 之间的数据传递</h1><p>我们之前实现了 activity 之间以及基于 fragment 的 activity 之间的数据传递。现在需实现同一 activity 托管的两个 fragment 之间的数据传递。<br><img src="http://img.blog.csdn.net/20161124133415509" alt="一个 activity 托管的不同 fragment 之间数据的传递"></p>
<h2 id="2-1-将数据传递到对话框"><a href="#2-1-将数据传递到对话框" class="headerlink" title="2.1 将数据传递到对话框"></a>2.1 将数据传递到对话框</h2><p>显然，要达到目的，只需要在 DatePickerFragment 中建立获取实例的 newInstance 方法，其中需要的参数是传递的信息即可，示例如下</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// DatePickerFragment.java</span></div><div class="line"></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> DatePickerFragment <span class="title">newInstance</span><span class="params">(Date date)</span> </span>&#123;</div><div class="line">	<span class="comment">// 新建一个 Bundle 对象用于存放数据</span></div><div class="line">	Bundle args = <span class="keyword">new</span> Bundle();</div><div class="line">	args.putSerializable(ARG_DATE, date);</div><div class="line">    DatePickerFragment fragment = <span class="keyword">new</span> DatePickerFragment();</div><div class="line">    <span class="comment">// 使用 fragment arguments 来传递参数</span></div><div class="line">    fragment.setArguments(args);</div><div class="line">    <span class="keyword">return</span> fragment;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<p>记得把使用 DatePickerFragment 的构造方法的地方改成 newInstance 方法获取实例。</p>
<p>在获得数据之后，要先将 DatePicker 初始化为原本的日期，首先用 Fragment 的 <code>getArguments().getSeriallizable(String key)</code> 方法获取数据，然后用 Calendar 对象取出 date 中的年月日，最后使用 DatePicker 类的 <code>init(int year, int month, int dayOfMonth, OnDateChangedListener listener)</code> 方法 初始化默认日期</p>
<h2 id="2-2-从对话框回传数据"><a href="#2-2-从对话框回传数据" class="headerlink" title="2.2 从对话框回传数据"></a>2.2 从对话框回传数据</h2><h3 id="2-2-1-设置目标-fragment"><a href="#2-2-1-设置目标-fragment" class="headerlink" title="2.2.1 设置目标 fragment"></a>2.2.1 设置目标 fragment</h3><p>类似于 activity 间的关联，可将 CrimeFragment 设置成 DatePickerFragment 的目标 fragment。 即使是在 CrimeFragment 和 DatePickerFragment 被销毁和重建后，操作系统也会重新关联它们。调用以下 Fragment 方法可建立这种关联:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setTargetFragment</span><span class="params">(Fragment fragment, <span class="keyword">int</span> requestCode)</span></span></div></pre></td></tr></table></figure>
<p>该方法有两个参数：目标 fragment 以及请求代码。需要时，目标 fragment 使用请求代码确认是哪个 fragment 在回传数据。</p>
<p>目标 fragment 和请求代码由 FragmentManager 负责跟踪管理,我们可调用设置目标的 fragment 的 <code>getTargetFragment()</code> 和 <code>getTargetRequestCode()</code> 方法获取它们。</p>
<h3 id="2-2-2-传递数据给目标-Fragment"><a href="#2-2-2-传递数据给目标-Fragment" class="headerlink" title="2.2.2 传递数据给目标 Fragment"></a>2.2.2 传递数据给目标 Fragment</h3><p>处理由同一 activity 托管的两个 fragment 间的数据返回时，可借用<code>Fragment.onActivityResult(...)</code>方法。因此，直接调用目标 fragment 的<code>Fragment.onActivityResult(...)</code>方法,，就能实现数据的回传。该方法恰好有我们需要的如下信息：</p>
<ul>
<li>请求代码：与传入<code>setTargetFragment(...)</code>方法相匹配,告诉目标 fragment 返回结果来 自哪里。</li>
<li>结果代码：决定下一步该采取什么行动。 </li>
<li>Intent：包含 extra 数据。</li>
</ul>
<p>所以从 CrimeFragment 中显示 DatePickerFragment，用户选择日期以后，想要回传信息，可以写一个 sendResult 方法，该方法如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// DatePickerFragment.java</span></div><div class="line"></div><div class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> String EXTRA_DATE =</div><div class="line">	<span class="string">"com.kniost.criminalintent.date"</span>;</div><div class="line"> ……                </div><div class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">sendResult</span><span class="params">(<span class="keyword">int</span> resultCode, Date date)</span> </span>&#123;</div><div class="line">	<span class="comment">// 防止出错</span></div><div class="line">	<span class="keyword">if</span> (getTargetFragment() == <span class="keyword">null</span>) &#123;</div><div class="line">		<span class="keyword">return</span>; </div><div class="line">	&#125;</div><div class="line">	Intent intent = <span class="keyword">new</span> Intent();</div><div class="line">	<span class="comment">// 放置数据到 Intent 中</span></div><div class="line">	intent.putExtra(EXTRA_DATE, date);</div><div class="line">	<span class="comment">// 获取目标 fragment，调用其 onActivityResult 方法，其中 RequestCode 是用 getTargetRequestCode 方法获取的，resultCode 是传入参数，intent 包含了数据</span></div><div class="line">	getTargetFragment()</div><div class="line">		.onActivityResult(getTargetRequestCode(), resultCode, intent);</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<p>在 CrimeFragment 中则应该重写 onActivityResult 方法：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// CrimeFragment.java</span></div><div class="line"></div><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onActivityResult</span><span class="params">(<span class="keyword">int</span> requestCode, <span class="keyword">int</span> resultCode, Intent data)</span> </span>&#123;</div><div class="line">	<span class="comment">// 如果一切正常，调用 sendResult 的方法时传入的参数应该就是 Activity.RESULT_OK，所以不会直接 return</span></div><div class="line">    <span class="keyword">if</span> (resultCode != Activity.RESULT_OK) &#123;</div><div class="line">        <span class="keyword">return</span>;</div><div class="line">	&#125;</div><div class="line">	</div><div class="line">	<span class="comment">// 如此判断方便有多个回传时使用</span></div><div class="line">	<span class="keyword">if</span> (requestCode == REQUEST_DATE) &#123;</div><div class="line">		Date date = (Date) data</div><div class="line">			.getSerializableExtra(DatePickerFragment.EXTRA_DATE);</div><div class="line">		mCrime.setDate(date); mDateButton.setText(mCrime.getDate().toString());</div><div class="line">	&#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<h1 id="3-挑战练习"><a href="#3-挑战练习" class="headerlink" title="3. 挑战练习"></a>3. 挑战练习</h1><p><code>//待完成</code></p>

          
        
      
    </div>

    <div>
      
    </div>

    <div>
      
    </div>

    <div>
      
    </div>

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal " itemscope itemtype="http://schema.org/Article">
    <link itemprop="mainEntityOfPage" href="https://kniost.github.io/2016-11-17-cj0ajvoun000g1js6ral6bwd6/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="kniost">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/images/avatar.jpg">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="结网之路">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
            
            
              
                
                <a class="post-title-link" href="/2016-11-17-cj0ajvoun000g1js6ral6bwd6/" itemprop="url">
                  Android编程权威指南（第二版）学习笔记（十一）—— 第11章 使用 ViewPager
                </a>
              
            
          </h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2016-11-17T11:31:10+08:00">
                2016-11-17
              </time>
            

            

            
          </span>

          
            <span class="post-category" >
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/Android编程权威指南学习笔记/" itemprop="url" rel="index">
                    <span itemprop="name">Android编程权威指南学习笔记</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
              <span class="post-comments-count">
                <span class="post-meta-divider">|</span>
                <span class="post-meta-item-icon">
                  <i class="fa fa-comment-o"></i>
                </span>
                <a href="/2016-11-17-cj0ajvoun000g1js6ral6bwd6/#comments" itemprop="discussionUrl">
                  <span class="post-comments-count disqus-comment-count" data-disqus-identifier="2016-11-17-cj0ajvoun000g1js6ral6bwd6/" itemprop="commentCount"></span>
                </a>
              </span>
            
          

          
          

          

          

          

        </div>
      </header>
    


    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <blockquote>
<p>本章介绍了如何使用 ViewPager（准确地说，应该是使用了 FragmentStatePagerAdapter 的简单的 ViewPager）。</p>
<p>GitHub 地址：<br><a href="https://github.com/kniost/Android-Programming-Big-Nerd-Ranch-Guide-2nd/commit/4e8b9cd914207d976b38d957375418d8d75b551b" target="_blank" rel="external">完成第十一章</a></p>
</blockquote>
<p>#1. ViewPager 和 PagerAdapter</p>
<p>ViewPager 在某种程度上类似于 RecyclerView，它们都需要借助于 Adapter 来支持，ViewPager 需要的是 PagerAdapter。</p>
<p>ViewPager 与 PagerAdapter 之间的配合比 RecyclerView 与其 Adapter 之间复杂得多。但是对于本章而言，因为使用的是 PagerAdapter 的子类 <code>FragmentStatePagerAdapter</code>，它能协助处理很多细节问题.</p>
<p><code>FragmentStatePagerAdapter</code> 化繁为简，提供了两个有用的方法：<code>getCount()</code> 和 <code>getItem (int)</code>。</p>
<p>调用  <code>getCount()</code> 方法顾名思义就是获取数据集的大小。调用 <code>getItem(int)</code> 方法，返回的是应该是和数据绑定的Fragment，一般来说会将其和数据集的位置相对应。</p>
<p>##使用步骤：</p>
<ul>
<li>布局文件，使用 ViewPager（因为只有支持包而没有内置，所以不像 fragment 需要选择）</li>
<li>在代码中声明 ViewPager 变量并引用</li>
<li>本书中使用的是匿名 FragmentStatePagerAdapter 类，在其中直接重写了两个关键方法，然后就可以使用了。</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div><div class="line">6</div><div class="line">7</div><div class="line">8</div><div class="line">9</div><div class="line">10</div><div class="line">11</div><div class="line">12</div><div class="line">13</div><div class="line">14</div><div class="line">15</div><div class="line">16</div><div class="line">17</div><div class="line">18</div><div class="line">19</div><div class="line">20</div><div class="line">21</div><div class="line">22</div><div class="line">23</div><div class="line">24</div><div class="line">25</div><div class="line">26</div><div class="line">27</div></pre></td><td class="code"><pre><div class="line"><span class="comment">// 引用 ViewPager</span></div><div class="line">mViewPager = (ViewPager) findViewById(R.id.activity_crime_pager_view_pager);</div><div class="line"><span class="comment">// 获取数据集</span></div><div class="line">mCrimes = CrimeLab.get(<span class="keyword">this</span>).getCrimes();</div><div class="line"><span class="comment">// 获取 FragmentManager</span></div><div class="line">FragmentManager fragmentManager = getSupportFragmentManager();</div><div class="line"><span class="comment">// 使用匿名内部类来引用 FragmentStatePagerAdapter，构造方法的参数是 FragmentManager</span></div><div class="line">mViewPager.setAdapter(<span class="keyword">new</span> FragmentStatePagerAdapter(fragmentManager) &#123;</div><div class="line">	<span class="meta">@Override</span></div><div class="line">	<span class="function"><span class="keyword">public</span> Fragment <span class="title">getItem</span><span class="params">(<span class="keyword">int</span> position)</span> </span>&#123;</div><div class="line">		Crime crime = mCrimes.get(position);</div><div class="line">		<span class="keyword">return</span> CrimeFragment.newInstance(crime.getId());</div><div class="line">	&#125;</div><div class="line"></div><div class="line">	<span class="meta">@Override</span></div><div class="line">	<span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">getCount</span><span class="params">()</span> </span>&#123;</div><div class="line">		<span class="keyword">return</span> mCrimes.size();</div><div class="line">	&#125;</div><div class="line">&#125;);</div><div class="line"></div><div class="line"><span class="comment">// 设置完 Adapter 以后，就要选择当前的数据啦~这就是上一个  Activity 传递进来的数据，我就不予赘述了。</span></div><div class="line"><span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; mCrimes.size(); i++) &#123;</div><div class="line">	<span class="keyword">if</span> (mCrimes.get(i).getId().equals(crimeId)) &#123;</div><div class="line">		mViewPager.setCurrentItem(i);</div><div class="line">		<span class="keyword">break</span>;</div><div class="line">	&#125;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<h1 id="2-FragmentStatePagerAdapter-与-FragmentPagerAdapter"><a href="#2-FragmentStatePagerAdapter-与-FragmentPagerAdapter" class="headerlink" title="2. FragmentStatePagerAdapter 与 FragmentPagerAdapter"></a>2. FragmentStatePagerAdapter 与 FragmentPagerAdapter</h1><p><code>FragmentPagerAdapter</code> 是另外一种可用的 PagerAdapter , 其用法与 <code>FragmentStatePagerAdapter</code> 基本一致。<strong>唯一的区别在于：卸载不再需要的 fragment 时，各自采用的处理方法有所不同。</strong></p>
<p><strong><code>FragmentStatePagerAdapter</code>会销毁不需要的 fragment</strong>。事务提交后，activity 的 <code>FragmentManager</code> 中的 fragment 会被彻底移除。<code>FragmentStatePagerAdapter</code> 类名中的“state”表明：在销毁 fragment 时，可在 <code>onSaveInstanceState(Bundle)</code> 方法中保存 fragment 的 Bundle 信息。用户切换回来时，保存的实例状态可用来恢复生成新的fragment。</p>
<p>相比之下，<code>FragmentPagerAdapter</code> 有不同的做法。对于不再需要的 fragment，<code>FragmentPagerAdapter</code> 会选择调用事务的 detach(Fragment) 方法来处理它,而非 remove(Fragment) 方 法。也就是说，<strong><code>FragmentPagerAdapter</code> 只是销毁了 fragment 的视图，fragment 实例还保留在 <code>FragmentManager</code> 中</strong>。因此，<code>FragmentPagerAdapter</code> 创建的 fragment 永远不会被销毁。</p>
<p>选择哪种adapter取决于应用的要求。通常来说，使用 <code>FragmentStatePagerAdapter</code> 更节省内存。CriminalIntent 应用需显示大量crime记录,每份记录最终还会包含图片。在内存中保存所有信息显然不合适,因此我们选择使用 <code>FragmentStatePagerAdapter</code> 。</p>
<p>另一方面,如果用户界面只需要少量固定的fragment，则 <code>FragmentPagerAdapter</code> 是个安全、 合适的选择。</p>
<p>最常见的例子为分页显示用户界面。例如，某些应用的明细视图所含内容较多，通 常需分两页显示。这时就可以将这些明细信息分拆开来，以多页面的形式展现。显然,为用户界面添加支持滑动切换的 ViewPager，能增强应用的触摸体验。此外，将 fragment 保存在内存中，更易于管理控制层的代码。对于这种类型的用户界面，每个 activity 通常只有两三个 fragment，基本不用担心有内存不足的风险。</p>
<h1 id="3-深入学习：ViewPager-的工作原理"><a href="#3-深入学习：ViewPager-的工作原理" class="headerlink" title="3. 深入学习：ViewPager 的工作原理"></a>3. 深入学习：ViewPager 的工作原理</h1><p>什么时候需要自己实现PagerAdapter接口呢？需要ViewPager托管非 fragment 视图时,就需要实现原生 PagerAdapter 接口。</p>
<p>PagerAdapter 要比 RecyclerView 的 Adapter复杂得多，因为它要处理更多的视图管理工作。<br>PagerAdapter 不使用可返回视图的<code>onBindViewHolder(...)</code>方法，而是使用下列方法:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div><div class="line">5</div></pre></td><td class="code"><pre><div class="line"><span class="function"><span class="keyword">public</span> Object <span class="title">instantiateItem</span><span class="params">(ViewGroup container, <span class="keyword">int</span> position)</span></span></div><div class="line"></div><div class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">destroyItem</span><span class="params">(ViewGroup container, <span class="keyword">int</span> position, Object object)</span></div><div class="line"></div><div class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">boolean</span> <span class="title">isViewFromObject</span><span class="params">(View view, Object object)</span></div></pre></td></tr></table></figure>
<ul>
<li><p><code>PagerAdapter.instantiateItem(ViewGroup, int)</code>方法告诉 PagerAdapter 创建指定位置的列表项视图，然后将其添加给 ViewGroup 视图容器，而 <code>destroyItem(ViewGroup, int, Object)</code> 方法则告诉 PagerAdapter 销毁已建视图。（注意，<code>instantiateItem(ViewGroup, int)</code> 方法并不要求立即创建视图。因此，PagerAdapter 可自行决定何时创建视图。）</p>
</li>
<li><p>视图创建完成后，ViewPager 会在某个时间点注意到它。为确定该视图所属的对象，ViewPager 会调用 <code>isViewFromObject(View, Object)</code> 方法。这 里 , Object 参数是 <code>instantiateItem(ViewGroup,int)</code> 方法返回的对象。因此,假设 ViewPager 调用<code>instantiateItem(ViewGroup, 5)</code> 方法返回一个 A 对象,那么只要传入的 View 参数是第5个对象的视图，<code>isViewFromObject(View, A)</code> 方法就应返回true值,否则返回false值。</p>
</li>
</ul>
<p>对 ViewPager 来说，这是一个复杂的过程，但对于PagerAdapter来说,这算不上什么。因为PagerAdapter只要能够创建、销毁视图以及识别视图来自哪个对象即可。这样的要求显然很宽松，因而PagerAdapter 能够比较自由地通过 <code>instantiateItem(ViewGroup, int)</code> 方法创建并添加新的fragment ,然后返回可以跟踪管理的 Object(fragment) 。</p>
<p>以下为isViewFromObject (View, Object)方法的具体实现:</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><div class="line">1</div><div class="line">2</div><div class="line">3</div><div class="line">4</div></pre></td><td class="code"><pre><div class="line"><span class="meta">@Override</span></div><div class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">isViewFromObject</span><span class="params">(View view, Object object)</span> </span>&#123;</div><div class="line">    <span class="keyword">return</span> ((Fragment)object).getView() == view;</div><div class="line">&#125;</div></pre></td></tr></table></figure>
<p>可以看到,每次需要使用ViewPager时,都要覆盖实现PagerAdapter的这些方法,这真是一种磨难。所幸我们有 <code>FragmentPagerAdapter</code> 和 <code>FragmentStatePagerAdapter</code> 便利类。</p>

          
        
      
    </div>

    <div>
      
    </div>

    <div>
      
    </div>

    <div>
      
    </div>

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </article>


    
  </section>

  
  <nav class="pagination">
    <a class="extend prev" rel="prev" href="/"><i class="fa fa-angle-left"></i></a><a class="page-number" href="/">1</a><span class="page-number current">2</span><a class="page-number" href="/page/3/">3</a><a class="page-number" href="/page/4/">4</a><a class="extend next" rel="next" href="/page/3/"><i class="fa fa-angle-right"></i></a>
  </nav>


          
          </div>
          


          

        </div>
        
          
  
  <div class="sidebar-toggle">
    <div class="sidebar-toggle-line-wrap">
      <span class="sidebar-toggle-line sidebar-toggle-line-first"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-middle"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-last"></span>
    </div>
  </div>

  <aside id="sidebar" class="sidebar">
    <div class="sidebar-inner">

      

      

      <section class="site-overview sidebar-panel sidebar-panel-active">
        <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
          <img class="site-author-image" itemprop="image"
               src="/images/avatar.jpg"
               alt="kniost" />
          <p class="site-author-name" itemprop="name">kniost</p>
           
              <p class="site-description motion-element" itemprop="description">kniost 的成长与疑惑</p>
          
        </div>
        <nav class="site-state motion-element">

          
            <div class="site-state-item site-state-posts">
              <a href="/archives">
                <span class="site-state-item-count">31</span>
                <span class="site-state-item-name">日志</span>
              </a>
            </div>
          

          
            
            
            <div class="site-state-item site-state-categories">
              <a href="/categories/index.html">
                <span class="site-state-item-count">3</span>
                <span class="site-state-item-name">分类</span>
              </a>
            </div>
          

          
            
            
            <div class="site-state-item site-state-tags">
              <a href="/tags/index.html">
                <span class="site-state-item-count">3</span>
                <span class="site-state-item-name">标签</span>
              </a>
            </div>
          

        </nav>

        
          <div class="feed-link motion-element">
            <a href="/atom.xml" rel="alternate">
              <i class="fa fa-rss"></i>
              RSS
            </a>
          </div>
        

        <div class="links-of-author motion-element">
          
        </div>

        
        

        
        

        


      </section>

      

      

    </div>
  </aside>


        
      </div>
    </main>

    <footer id="footer" class="footer">
      <div class="footer-inner">
        <div class="copyright" >
  
  &copy; 
  <span itemprop="copyrightYear">2017</span>
  <span class="with-love">
    <i class="fa fa-heart"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">kniost</span>
</div>


<div class="powered-by">
  由 <a class="theme-link" href="https://hexo.io">Hexo</a> 强力驱动
</div>

<div class="theme-info">
  主题 -
  <a class="theme-link" href="https://github.com/iissnan/hexo-theme-next">
    NexT.Pisces
  </a>
</div>


        

        
      </div>
    </footer>

    
      <div class="back-to-top">
        <i class="fa fa-arrow-up"></i>
        
      </div>
    
    
  </div>

  

<script type="text/javascript">
  if (Object.prototype.toString.call(window.Promise) !== '[object Function]') {
    window.Promise = null;
  }
</script>









  




  
  <script type="text/javascript" src="/lib/jquery/index.js?v=2.1.3"></script>

  
  <script type="text/javascript" src="/lib/fastclick/lib/fastclick.min.js?v=1.0.6"></script>

  
  <script type="text/javascript" src="/lib/jquery_lazyload/jquery.lazyload.js?v=1.9.7"></script>

  
  <script type="text/javascript" src="/lib/velocity/velocity.min.js?v=1.2.1"></script>

  
  <script type="text/javascript" src="/lib/velocity/velocity.ui.min.js?v=1.2.1"></script>

  
  <script type="text/javascript" src="/lib/fancybox/source/jquery.fancybox.pack.js?v=2.1.5"></script>


  


  <script type="text/javascript" src="/js/src/utils.js?v=5.1.0"></script>

  <script type="text/javascript" src="/js/src/motion.js?v=5.1.0"></script>



  
  


  <script type="text/javascript" src="/js/src/affix.js?v=5.1.0"></script>

  <script type="text/javascript" src="/js/src/schemes/pisces.js?v=5.1.0"></script>



  

  


  <script type="text/javascript" src="/js/src/bootstrap.js?v=5.1.0"></script>



  



  

    <script type="text/javascript">
      var disqus_shortname = 'kniost-comment-github';
      var disqus_identifier = 'page/2/index.html';

      var disqus_title = "";


      function run_disqus_script(disqus_script) {
        var dsq = document.createElement('script');
        dsq.type = 'text/javascript';
        dsq.async = true;
        dsq.src = '//' + disqus_shortname + '.disqus.com/' + disqus_script;
        (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
      }

      run_disqus_script('count.js');

      

    </script>
  










  
  

  

  

  

  


  

</body>
</html>
